Descripcion base de datos

Titulo de la base de datos:

Forest Covertype data, Remote Sensing and GIS Program Department of Forest Sciences College of Natural Resources Colorado State University.

El dataset consiste en medidas cartograficas de areas silvestres en el Bosque Nacional Roosevelt en el norte de Colorado en USA y el tipo de covertura vegetal presente. Se realizan en celdas de 30 X 30 metros. Estos bosques tienen poca intervencion humana, por ende las areas caracterizan no usos (como agricultura, poblacion etc) sino clase de bosque predominante. En total son 12 medidas cartograficas y 7 grandes tipos de covertura vegetal.

El dataset:

Numero de columnas:

length(df)
[1] 55

Numero de observaciones:


nrow(df)
[1] 581011

Atributos:

names(df[,c(1:10,55)])
 [1] "Elevation"                          "Aspect"                            
 [3] "Slope"                              "Horizontal_Distance_To_Hydrology"  
 [5] "Vertical_Distance_To_Hydrology"     "Horizontal_Distance_To_Roadways"   
 [7] "Hillshade_9am"                      "Hillshade_Noon"                    
 [9] "Hillshade_3pm"                      "Horizontal_Distance_To_Fire_Points"
[11] "Forest_Cover_Type"                 

Otros atributos son cuatro areas silvestres y 40 tipos de suelo.

El tipo de atributo, las unidades y su descripcion:

Name Data Type Measurement Description
Elevation quantitative meters Elevation in meters
Aspect quantitative azimuth Aspect in degrees azimuth
Slope quantitative degrees Slope in degrees
Horizontal_Distance_To_Hydrology quantitative meters Horz Dist to nearest surface water features
Vertical_Distance_To_Hydrology quantitative meters Vert Dist to nearest surface water features
Horizontal_Distance_To_Roadways quantitative meters Horz Dist to nearest roadway
Hillshade_9am quantitative 0 to 255 index Hillshade index at 9am, summer solstice
Hillshade_Noon quantitative 0 to 255 index Hillshade index at noon, summer soltice
Hillshade_3pm quantitative 0 to 255 index Hillshade index at 3pm, summer solstice
Horizontal_Distance_To_Fire_Points quantitative meters Horz Dist to nearest wildfire ignition points
Wilderness_Area (4 binary columns) qualitative 0 (absence) or 1 (presence) Wilderness area designation
Soil_Type (40 binary columns) qualitative 0 (absence) or 1 (presence Soil Type designation
Cover_Type (7 types) integer 1 to 7 Forest Cover Type designation

Las cuatro areas silvestres:

  • Rawah Wilderness Area
  • Neota Wilderness Area
  • Comanche Peak Wilderness Area
  • Cache la Poudre Wilderness Area

Tipo de covertura forestal:

  • Spruce/Fir (picea -pino-/abeto)
  • Lodgepole Pine (pino)
  • Ponderosa Pine (pino)
  • Cottonwood/Willow (sauces)
  • Aspen (alamos)
  • Douglas-fir (pino oregon)
  • Krummholz (vegetación atrofiada y deformada, arbol “bandera”)

Missings

No hay missings.

colSums(is.na(df))
                         Elevation                             Aspect 
                                 0                                  0 
                             Slope   Horizontal_Distance_To_Hydrology 
                                 0                                  0 
    Vertical_Distance_To_Hydrology    Horizontal_Distance_To_Roadways 
                                 0                                  0 
                     Hillshade_9am                     Hillshade_Noon 
                                 0                                  0 
                     Hillshade_3pm Horizontal_Distance_To_Fire_Points 
                                 0                                  0 
                 Wilderness_Area_1                  Wilderness_Area_2 
                                 0                                  0 
                 Wilderness_Area_3                  Wilderness_Area_4 
                                 0                                  0 
                        Soil_Type1                         Soil_Type2 
                                 0                                  0 
                        Soil_Type3                         Soil_Type4 
                                 0                                  0 
                        Soil_Type5                         Soil_Type6 
                                 0                                  0 
                        Soil_Type7                         Soil_Type8 
                                 0                                  0 
                        Soil_Type9                        Soil_Type10 
                                 0                                  0 
                       Soil_Type11                        Soil_Type12 
                                 0                                  0 
                       Soil_Type13                        Soil_Type14 
                                 0                                  0 
                       Soil_Type15                        Soil_Type16 
                                 0                                  0 
                       Soil_Type17                        Soil_Type18 
                                 0                                  0 
                       Soil_Type19                        Soil_Type20 
                                 0                                  0 
                       Soil_Type21                        Soil_Type22 
                                 0                                  0 
                       Soil_Type23                        Soil_Type24 
                                 0                                  0 
                       Soil_Type25                        Soil_Type26 
                                 0                                  0 
                       Soil_Type27                        Soil_Type28 
                                 0                                  0 
                       Soil_Type29                        Soil_Type30 
                                 0                                  0 
                       Soil_Type31                        Soil_Type32 
                                 0                                  0 
                       Soil_Type33                        Soil_Type34 
                                 0                                  0 
                       Soil_Type35                        Soil_Type36 
                                 0                                  0 
                       Soil_Type37                        Soil_Type38 
                                 0                                  0 
                       Soil_Type39                        Soil_Type40 
                                 0                                  0 
                 Forest_Cover_Type 
                                 0 

###Variables categoricas###

Una de las variables consiste en medidas del tipo de suelo presente. En el dataset esta variable se mapeo a 40 one-hot variables. Cada variable corresponde a un tipo de suelo, catalogado con un codigo que incluye informacion de la zona climatica y zona geologica. La distribucion de esta variable es la siguiente :

barplot(sort(table(df_suelos$suelo)),las=2, log="y", xlab = "suelo", col = "#1b98e0")
png('barsuelo.png')
barplot(sort(table(df_suelos$suelo)),las=2, log="y", xlab = "suelo", col = "#1b98e0")

dev.off()
png 
  2 

El suelo mas abundante tiene el doble de frecuencia que el siguiente mas abundante y su frecuencia esta 4 ordenes de magnitud por encima del suelo menos frecuente. En vista de que son muchas variables individuales a ser consideradas y no pueden ser combinadas (no sin conocimiento experto en geologia) para reducir su numero. No sera considerada en el analisis.

Otra de las variables categoricas es la zona silvestre de la celda. La abundancia de zonas:

barplot(sort(table(df_areas$areas)),las=2, xlab = "areas silvestres", col = "#F4A582")

png('barareas.png')

barplot(sort(table(df_areas$areas)),las=2, xlab = "areas silvestres", col = "#F4A582")

dev.off()
png 
  2 

Las areas silvestres 3 y 4 son alrededor de 8 veces mas abundantes en el dataset que las 1 y 2. Tambien se podria usar para clasificar pero se escoge la clase de cobertura forestal.

Descripcion de la variable objetivo, las clases de cobertura vegetal:

library(plotly)

labels = c('Spruce/Fir','Lodgepole Pine','Ponderosa Pine','Cottonwood/Willow','Aspen','Douglas-fir',
          'Krummholz')
values <- aggregate(df$Elevation~ df$Forest_Cover_Type, data=df, FUN=length)
fig <- plot_ly(type='pie', labels=labels, values=values$`df$Elevation`, 
               textinfo='label+percent',
               insidetextorientation='radial',showlegend = FALSE)
fig


#png('covertype.png')

#fig

#dev.off()

#pie(table(df$Forest_Cover_Type),main='Tipos de covertura')
#barplot(table(df$World.region), xlab = "Region", ylab = "Cantidad de Ciudades",         main="Ciudades por region",cex.names = .3, las=2) 

Se puede observar que los datos estan bastante desbalanceados. Esto incidiría en una regresión pero no en un LDA.

Resumen de los datos:

df_max<-apply(df[,1:10],2,max)
df_min<-apply(df[,1:10],2,min)
df_n <- sweep(df[,1:10], 2, df_min, "-")
range <- df_max-df_min
df_n <- sweep(df_n, 2, range, "/")
boxplot(df_n,las=2)

Todas las distribuciones son sesgadas y tienen muchos outliers. Para clasificar, si se va a aplicar un LDA, hay que garantizar normalidad y que las medias sean distintas, pero se vera mas adelante. Tambien es importante garantizar que los atributos sean independientes.

La variable Aspect es bimodal y probablemente Slope es multimodal. Todas las variables tienen colas largas, excepto Hillshade_3pm. En todas las variables excepto en Elevation las clases siguen la misma tendencia. En esta variable las distribuciones para cada grupo estan centradas distinto:

ggplot2.multiplot(plot1,plot2,plot3,plot4,plot5,plot6,plot7,plot8,plot9,plot10, cols=4)

png('histograms.png')

ggplot2.multiplot(plot1,plot2,plot3,plot4,plot5,plot6,plot7,plot8,plot9,plot10, cols=3)

dev.off()
png 
  2 

Correlaciones

library(ggcorrplot)

corr <- round(cor(subset0[,1:10]), 1)
p.mat<- cor_pmat(subset0[,1:10])

ggcorrplot(corr, lab = TRUE,
     outline.col = "white",lab_size = 3, tl.cex=5,method = "circle",hc.order = TRUE, p.mat = p.mat)
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
png('correlation.png')

ggcorrplot(corr, lab = TRUE,
     outline.col = "white",lab_size = 3, tl.cex=5, method = "circle",hc.order = TRUE, p.mat = p.mat)
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.
dev.off()
png 
  2 

Las variables hillshade estan relacionadas con aspect, slope y entre ellas. Esto tiene que ver con como se calculan las cantidades hillshade que son los sombreados que se dibujan sobre los mapas cartograficos. Se supone una iluminacion simulada que depende de la orientacion a la fuente de luz, la cual esta basada en las variables aspect y slope. Las cantidades distancia vertical y horizontal a cursos de agua tambien estan relacionadas y se calculan con slope y elevation. La distancia horizontal a caminos y a puntos de fuego tambien se calculan con la elevacion y estan relacionados entre si.

Con base en esto acoto las variables a Elevation, Aspect, Slope, Hillshade_noon, Horizontal_Distance_Roadways y Horizontal_Distance_to_Hydrology.

library(GGally)
library(data.table)

color <- as.factor(subset[,7])
ggpairs(subset[,1:5], aes(color = color, alpha = 0.5),lower = list(combo = "count"),upper = "blank",)

Se pueden observar tambien las distribuciones de las clases, son sesgadas como se vio en los boxplots. La variable que podria servir mas para distinguir clases podria ser Elevation.

subsubset <- subset[,c(1:4,6,8,11)]

Analisis explotario con PCA

library(ggbiplot)
library(plyr)

datos_pca <- prcomp(subset[,1:5,7],scale=TRUE)
p <- ggbiplot(datos_pca, obs.scale=0.01,alpha = 0.3,groups=subset$cover_type)
p <- p + xlim(-3, 2) + ylim(-2, 3)

plot(p)

png('pca.png')

plot(p)

dev.off()
png 
  2 

Clasificacion Supervisada

Linear Discriminant Analysis

El primer metodo a implementar es LDA. Para que tenga validez el metodo deben cumplirse los supuestos de normalidad multivariada para cada nivel de cada variable y de homocedasticidad.

Para el test de normalidad multivariada usamos test de Shapiro:

library(mvnormtest)
test <- mshapiro.test(t(subsubset[subsubset$cover_type=='1',1:6]))
shapitest <- test$p.value
test <- mshapiro.test(t(subsubset[subsubset$cover_type=='2',1:6]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subsubset[subsubset$cover_type=='3',1:6]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subsubset[subsubset$cover_type=='4',1:6]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subsubset[subsubset$cover_type=='5',1:6]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subsubset[subsubset$cover_type=='6',1:6]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subsubset[subsubset$cover_type=='7',1:6]))
shapitest <- append(shapitest,test$p.value)

shapitest
[1] 5.153787e-22 9.523226e-34 3.087307e-06 2.832202e-08 3.613977e-06 9.314272e-13 1.285806e-07

Rechaza normalidad para todos los niveles de todas las variables. Para el total:

library(mvnormtest)
mshapiro.test(t(subset[,1:6]))

    Shapiro-Wilk normality test

data:  Z
W = 0.86531, p-value < 2.2e-16

Si el nivel de significacion por defecto es de 0.05, rechaza la normalidad multivariada.

El siguiente supuesto de homocedasticidad lo evaluamos con el test M de Box:

library(biotools)
Loading required package: MASS
---
biotools version 4.2
boxM(data=subsubset[,1:6],grouping=subsubset[,7])

    Box's M-test for Homogeneity of Covariance Matrices

data:  subsubset[, 1:6]
Chi-Sq (approx.) = 1840.7, df = 126, p-value < 2.2e-16

Rechaza la homogeneidad de varianzas. Se tendria que realizar un LDA robusto (cuadratico).

Igual, a pesar de haber rechazado la normalidad hacemos el test de Hotelling para determinar si las medias de los grupos son distintas:

library(Hotelling)
Loading required package: corpcor
fitProd = hotelling.test(.~ cover_type, data = subsubset) 
fitProd
Test stat:  1813.2 
Numerator df:  6 
Denominator df:  4230 
P-value:  0 

Por ende rechazamos que las medias sean iguales. El cover_type es discriminante.

LDA

Habiendo determinado que la variable de clase es discriminante, calculamos el lda:


library(MASS)

modelo_lda <- lda(formula = cover_type ~ Elevation + Aspect + Slope + Horizontal_Distance_To_Hydrology + Horizontal_Distance_To_Roadways + Hillshade_Noon, data = subset)

Ahora se prueba el modelo:

predicciones <-  predict(object = modelo_lda, newdata = testeo, method = "predictive")
table(clase, predicciones$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2   3   4   5   6   7
         1 510 203   0   0   0   0   7
         2 199 791  11   0   0  10   0
         3   0  24  77   1   0   6   0
         4   0   0  10   1   0   0   0
         5   1  32   0   0   0   0   0
         6   0  20  45   0   0   8   0
         7  62   2   0   0   0   0   5

Los errores de prediccion:

training_error <- mean(clase != predicciones$class) * 100 
training_error 
[1] 31.25926

El error da bastante grande…

Con una variable mas:

library(klaR) 

testeo$clase <- as.factor(testeo$clase)

partimat(clase ~ ., data = testeo, method = "lda", col.correct=NA, col.wrong=NA,plot.matrix=FALSE,image.colors =rainbow(7))

Sin esa variable:

library(klaR) 

testeo$clase <- as.factor(testeo$clase)

partimat(clase ~ ., data = testeo[,c(1:5,7)], method = "lda", col.correct=NA, col.wrong=NA,plot.matrix=FALSE,image.colors =rainbow(7),scaled=TRUE)

png('partiplot.png')

partimat(clase ~ ., data = testeo[,c(1:5,7)], method = "lda", col.correct=NA, col.wrong=NA,plot.matrix=FALSE,image.colors =rainbow(7),scaled=TRUE)

dev.off()
png 
  2 

Discrimina muy mal. Ahora, si transformo las variables con un log:

library(IDPmisc)

df_trans <- log(subset[,1:6])
subset_trans <- cbind(df_trans,cover_type)
subset_t <- NaRV.omit(subsubset_trans)
subset_t$cover_type <- as.factor(subset_t$cover_type)
library(MASS)

modelo_lda_trans <- lda(formula = cover_type ~. , data = subset_t)
library(IDPmisc)
library(dplyr)

test_trans <- log(test)
test_t <- cbind(test_trans,clase)
test_t <- NaRV.omit(test_t)
clase_t <-test_t$clase
predicciones_t <-  predict(object = modelo_lda_trans, newdata = test_t)
table(clase_t, predicciones_t$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2   3   4   5   6   7
         1   0 682   0   0   0   0   0
         2   0 969   0   0   0   0   0
         3   0 103   0   0   0   0   0
         4   0   8   0   0   0   0   0
         5   0  29   0   0   0   0   0
         6   0  68   0   0   0   0   0
         7   0  67   0   0   0   0   0
training_error_t <- mean(clase_t != predicciones_t$class) * 100 
training_error_t 
[1] 49.68847

Pesimo.

Si considero una variable mas, sin transformar:


library(MASS)

modelo_lda_2 <- lda(formula = cover_type ~. , data = subset_2)
predicciones_2 <-  predict(object = modelo_lda_2, newdata = testeo_2)
table(testeo_2$clase, predicciones_2$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2   3   4   5   6   7
         1 516 200   0   0   0   0   4
         2 201 790  11   0   0   9   0
         3   0  25  69   4   0  10   0
         4   0   0  10   1   0   0   0
         5   0  33   0   0   0   0   0
         6   0  19  44   0   0  10   0
         7  63   1   0   0   0   0   5
training_error_2 <- mean(testeo_2$clase != predicciones_2$class) * 100 
training_error_2 
[1] 31.30864

Considerar las variables con logaritmo o considerar una variable adicional no mejoro la prediccion. Se intentara ahora con un lda robusto:


library(MASS)

modelo_qda <- qda(formula = cover_type ~ ., data = subset)
predicciones_qda <-  predict(object = modelo_qda, newdata = testeo, method = "predictive")
table(clase, predicciones_qda$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2   3   4   5   6   7
         1  66  15   0   4 161  37 437
         2  92  66   1  30 572  86 164
         3   0   0   3  68   8  29   0
         4   0   0   0  11   0   0   0
         5   2   0   0   0  29   2   0
         6   0   0   4  37   7  25   0
         7   0   0   0   0   1   0  68
training_error_qda <- mean(clase != predicciones_qda$class) * 100 
training_error_qda
[1] 86.76543

Da peor…

Con las clases escaladas:

datos_escalados <- as.data.frame(scale(subset[,1:5]))
subset_esc <- cbind(datos_escalados,cover_type)
subset_esc$cover_type <- as.factor(subset_esc$cover_type)

test de Shapiro para los datos escalados:

subset_esc$cover_type <- as.factor(subset_esc$cover_type)
library(mvnormtest)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='1',1:5]))
shapitest <- test$p.value
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='2',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='3',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='4',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='5',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='6',1:5]))
shapitest <- append(shapitest,test$p.value)
test <- mshapiro.test(t(subset_esc[subset_esc$cover_type=='7',1:5]))
shapitest <- append(shapitest,test$p.value)

shapitest
[1] 5.239585e-15 2.098729e-18 1.165629e-01 5.931935e-04 1.401489e-09 1.025656e-02 1.953662e-10

library(MASS)

modelo_lda_esc <- lda(formula = cover_type ~., data = subset_esc)
test_esc <- as.data.frame(scale(test))
test_esc <- cbind(test_esc,clase)
test_esc$clase <- as.factor(test_esc$clase)
predicciones_esc <-  predict(object = modelo_lda_esc, newdata = test_esc, method = "predictive")
table(clase, predicciones_esc$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2   3   4   5   6   7
         1 521 192   0   0   0   0   7
         2 216 774  11   0   0  10   0
         3   0  25  76   1   0   6   0
         4   0   0  10   1   0   0   0
         5   1  32   0   0   0   0   0
         6   0  21  43   0   0   9   0
         7  63   1   0   0   0   0   5
training_error_esc <- mean(clase != predicciones_esc$class) * 100 
training_error_esc
[1] 31.55556

El mejor resultado fue el inicial. En general se predicen muy mal las clases menos abundantes.

El qda con los datos escalados:


library(MASS)

modelo_qda_esc <- qda(formula = cover_type ~ ., data = subset[,1:5,7])
predicciones_qda_esc <-  predict(object = modelo_qda_esc, newdata = testeo[,1:5,7], method = "predictive")
table(clase, predicciones_qda_esc$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2   3   4   5   6   7
         1 115  21   0   2 168  10 404
         2 129  99   0  15 552  74 142
         3   0   0   6  63   8  31   0
         4   0   0   0  10   0   1   0
         5   2   0   0   0  29   2   0
         6   0   0   0  39   7  27   0
         7   0   0   0   0   1   0  68
training_errorqda_esc <- mean(clase != predicciones_qda_esc$class) * 100 
training_errorqda_esc
[1] 82.51852

Se realizara el analisis con solo dos clases:

subsubset2 <-subset[(subset$cover_type==2),]
subsubset1 <-subset[(subset$cover_type==1),]
subsubset <-rbind(subsubset1,subsubset2)
subsubset$cover_type <- droplevels(subsubset$cover_type)

El test de normalidad multivariada:

library(mvnormtest)

testing <- mshapiro.test(t(subsubset[subsubset$cover_type=='1',1:6]))
shapitest <- testing$p.value
testing <- mshapiro.test(t(subsubset[subsubset$cover_type=='2',1:6]))
shapitest <- append(shapitest,testing$p.value)

shapitest
[1] 2.929115e-30 3.038726e-36

Rechaza normalidad para cada variable para cada nivel de la variable.

Con las variables estandarizadas:

library(clusterSim)

#subsubset_es <- scale(subsubset[,1:6], center = median(subsubset))
subsubset_es <- data.Normalization (subsubset[,1:6],type="n2",normalization="column")
subsubset_es <- cbind(subsubset_es,subsubset[,7])

Con las variables estandarizadas y sacando hillshade:

library(mvnormtest)

subsubset_2 <- subsubset_es[,c(1:5,7)]
testing <- mshapiro.test(t(subsubset_2[subsubset_2$cover_type=='1',1:5]))
shapitest <- testing$p.value
testing <- mshapiro.test(t(subsubset_2[subsubset_2$cover_type=='2',1:5]))
shapitest <- append(shapitest,testing$p.value)

shapitest
[1] 5.239585e-15 2.098729e-18

Rechaza, sin sacar y sacando hillshade.

Testeando homocedasticidad:

library(biotools)

boxM(data=subsubset[,1:6],grouping=subsubset[,7])

Rechaza igualdad de varianzas.

Como el test M de Box es sensible a la falta de normalidad, realizamos el de Levene:

library(car)
Loading required package: carData

Attaching package: ‘car’

The following object is masked from ‘package:dplyr’:

    recode
leveneTest( Elevation + Aspect + Slope + Horizontal_Distance_To_Hydrology + Horizontal_Distance_To_Roadways + Hillshade_Noon ~ subsubset$cover_type, data = subsubset)
Levene's Test for Homogeneity of Variance (center = median)
        Df F value    Pr(>F)    
group    1  24.874 6.365e-07 ***
      4268                      
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

El valor es menor que el nivel de significancia 0.001. Rechazamos la hipotesis nula y concluimos que las varianzas no son iguales.

De todas maneras hacemos el test de Hotelling para testear diferencia de medias:

library(Hotelling)

fitProd = hotelling.test(.~ subsubset$cover_type, data = subsubset) 
fitProd

Rechaza igualdad de medias, pero como no se cumple la normalidad multivariada este resultado no es confiable.

LDA con dos clases

el conjunto de test para dos clases:

library(dplyr)

testeo2 <-testeo[(testeo$clase==2),]
testeo1 <-testeo[(testeo$clase==1),]
testeo_2class <-rbind(testeo1,testeo2)
testeo_2class$clase <- droplevels(testeo_2class$clase)
clase_2class <- testeo_2class[,7]
test_2class <-testeo_2class[,1:6]

library(MASS)

modelo_lda_2class <- lda(formula = cover_type ~. , data = subsubset)

la clasificacion ingenua:

predicciones_2class_0 <-  predict(object = modelo_lda_2class, newdata = subsubset)
table(subsubset$cover_type, predicciones_2class_0$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real    1    2
         1 1343  488
         2  525 1914
training_error_2cl_0 <- mean(subsubset$cover_type != predicciones_2class_0$class) * 100 
training_error_2cl_0
[1] 23.72365
predicciones_2class <-  predict(object = modelo_lda_2class, newdata = testeo_2class)
table(clase_2class, predicciones_2class$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2
         1 516 204
         2 199 812
training_error_2cl <- mean(clase_2class != predicciones_2class$class) * 100 
training_error_2cl
[1] 23.28134
library(klaR) 

partimat(subsubset$cover_type ~ ., data = subsubset, method = "lda", col.correct=NA, col.wrong=NA, plot.matrix=FALSE,image.colors =rainbow(2))

La variable que mejor separa es elevacion.

Con las variables estandarizadas:


library(MASS)

modelo_lda_2class_est <- lda(formula = cover_type ~. , data = subsubset_es)

la clasificacion ingenua:

predicciones_2class_es_0 <-  predict(object = modelo_lda_2class_est, newdata = subsubset_es)
table(subsubset_es$cover_type, predicciones_2class_es_0$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real    1    2
         1 1343  488
         2  525 1914
training_error_2cl_es_0 <- mean(subsubset_es$cover_type != predicciones_2class_es_0$class) * 100 
training_error_2cl_es_0
[1] 23.72365
library(clusterSim)

#subsubset_es <- scale(subsubset[,1:6], center = median(subsubset))
testeo_es <- data.Normalization (testeo_2class[,1:6],type="n2",normalization="column")
testeo_es <- cbind(testeo_es,testeo_2class$clase)
testeo_es <- rename(testeo_es, cover_type=`testeo_2class$clase`)
predicciones_2class_es <-  predict(object = modelo_lda_2class_est, newdata = testeo_es)
table(testeo_es$clase, predicciones_2class_es$class, dnn = c("Clase real", "Clase predicha"))
training_error_2cl_es <- mean(testeo_es$clase != predicciones_2class_es$class) * 100 
training_error_2cl_es
[1] 23.62796

Haciendo el discriminante robusto:


library(MASS)

modelo_qda_2class_est <- qda(formula = cover_type ~. , data = subsubset_es)
prediccionesqda_2c_es_0 <-  predict(object = modelo_qda_2class_est, newdata = subsubset_es)
table(subsubset_es$cover_type, prediccionesqda_2c_es_0$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real    1    2
         1 1397  434
         2  613 1826
training_errorqda_2cl_es_0 <- mean(subsubset_es$cover_type != prediccionesqda_2c_es_0$class) * 100 
training_errorqda_2cl_es_0
[1] 24.51991
prediccionesqda_2c_es <-  predict(object = modelo_qda_2class_est, newdata = testeo_es)
table(testeo_es$clase, prediccionesqda_2c_es$class, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2
         1 559 161
         2 252 759
training_errorqda_2cl_es <- mean(testeo_es$clase != prediccionesqda_2c_es$class) * 100 
training_errorqda_2cl_es
[1] 23.85904

Incluso empeora un poco.

Support Vector machine

Clasificando con svm el dataset con las 7 clases:

library(e1071)

model.svm = svm( subset$cover_type ~ ., data = subset[,c(1:5,7)], kernel = "radial", cost = 10, scale = TRUE)
print(model.svm)

Call:
svm(formula = subset$cover_type ~ ., data = subset[, c(1:5, 7)], kernel = "radial", cost = 10, 
    scale = TRUE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  10 

Number of Support Vectors:  3264
predicciones_svm = predict(model.svm, subset[,c(1:5,7)])

table(subset$cover_type, predicciones_svm, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real    1    2    3    4    5    6    7
         1 1342  465    0    0    0    0   24
         2  373 2035   24    0    0    4    3
         3    0   60  225    0    0    9    0
         4    0    0    7   13    0    1    0
         5    0   60    0    0   18    0    0
         6    0   66   46    0    0   35    0
         7   86    4    0    0    0    0  100
training_error_svm_0 <- mean(subset$cover_type != predicciones_svm) * 100 
training_error_svm_0
[1] 24.64

El kernel que mejor da es el radial.

predicciones_svm_te = predict(model.svm, testeo)

table(testeo$clase, predicciones_svm_te, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2   3   4   5   6   7
         1 499 209   0   0   0   0  12
         2 169 821  13   0   3   4   1
         3   0  26  76   1   0   5   0
         4   0   0   8   3   0   0   0
         5   0  29   0   0   4   0   0
         6   0  27  38   0   0   8   0
         7  37   3   0   0   0   0  29
training_error_svm <- mean(testeo$clase != predicciones_svm_te) * 100 
training_error_svm
[1] 28.88889

Si lo hacemos con menos variables:

subsubset1 <-subset[(subset$cover_type==1),]
subsubset2 <-subset[(subset$cover_type==2),]
subsubset <-rbind(subsubset1,subsubset2)
subsubset$cover_type <- droplevels(subsubset$cover_type)
library(e1071)

model.svm_2 = svm( subsubset$cover_type ~ ., data = subsubset[,c(1:5,7)], kernel = "radial", cost = 10, scale = TRUE)
print(model.svm)

Call:
svm(formula = subset$cover_type ~ ., data = subset[, c(1:5, 7)], kernel = "radial", cost = 10, 
    scale = TRUE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  10 

Number of Support Vectors:  3264
predicciones_svm_2 = predict(model.svm_2, subsubset[,c(1:5,7)])

table(subsubset$cover_type, predicciones_svm_2, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real    1    2
         1 1359  472
         2  374 2065
training_error_svm_2 <- mean(subsubset$cover_type != predicciones_svm_2) * 100 
training_error_svm_2
[1] 19.81265

Ahora con el test:

predicciones_svm_te_2 = predict(model.svm_2, testeo_2class)

table(testeo_2class$clase, predicciones_svm_te_2, dnn = c("Clase real", "Clase predicha"))
          Clase predicha
Clase real   1   2
         1 501 219
         2 170 841
training_error_svm_2_te <- mean(testeo_2class$clase != predicciones_svm_te_2) * 100 
training_error_svm_2_te
[1] 22.47256

Graficando para todas las clases:

plot(model.svm, data=subset[,c(1:5,7)],Elevation ~ Aspect,  color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Elevation ~ Slope, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Aspect ~ Slope, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm, data=subset[,c(1:5,7)], Horizontal_Distance_To_Hydrology ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)],Elevation ~ Aspect,  color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Elevation ~ Slope, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Elevation ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Aspect ~ Slope, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Aspect ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Hydrology, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Slope ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

plot(model.svm_2, data=subsubset[,c(1:5,7)], Horizontal_Distance_To_Hydrology ~ Horizontal_Distance_To_Roadways, color.palette=topo.colors)

Se dibujan mas fronteras cuando la variable Elevation esta presente.

Clasificacion no supervisada

Para la clasificacion no supervisada, comenzamos con metodos jerarquicos porque k-means es sensible a outliers:

datos_clust <- subset
mat_dist <- dist(x = subset, method = "euclidean") 

Dendrogramas para distintos metodos, suponemos 7 clusters:

hc_complete <- hclust(d = mat_dist, method = "complete") 
hc_average  <- hclust(d = mat_dist, method = "average")
hc_single   <- hclust(d = mat_dist, method = "single")
hc_ward     <- hclust(d = mat_dist, method = "ward.D2")
hc_cn     <- hclust(d = mat_dist, method = "centroid")

Construyendo los dendrogramas:

cantidad_clusters = 7

plot(hc_complete)
rect.hclust(hc_complete, k=cantidad_clusters, border="red") #


jer_complete<-cutree(hc_complete,k=cantidad_clusters)           #
datos_clust$jer_complete=jer_complete
cantidad_clusters = 7

plot(hc_average)
rect.hclust(hc_average, k=cantidad_clusters, border="red") #


jer_average<-cutree(hc_average,k=cantidad_clusters)           #
datos_clust$jer_average=jer_average
cantidad_clusters = 7

plot(hc_single)
rect.hclust(hc_single, k=cantidad_clusters, border="red") #


jer_single<-cutree(hc_single,k=cantidad_clusters)           #
#datos$jer_complete=jer_complete

Da feisimo…

cantidad_clusters = 7

plot(hc_ward)
rect.hclust(hc_ward, k=cantidad_clusters, border="red") #


jer_ward<-cutree(hc_ward,k=cantidad_clusters)           #
datos_clust$jer_ward=jer_ward
cantidad_clusters = 7

plot(hc_cn)
rect.hclust(hc_cn, k=cantidad_clusters, border="red") #


jer_cn<-cutree(hc_cn,k=cantidad_clusters)           #
datos_clust$jer_cn=jer_cn

Los coeficientes de correlacion cofenetica:

cor(x = mat_dist, cophenetic(hc_complete))
[1] 0.7768551
cor(x = mat_dist, cophenetic(hc_average))
[1] 0.7736942
cor(x = mat_dist, cophenetic(hc_single))
[1] 0.1769797
cor(x = mat_dist, cophenetic(hc_ward))
[1] 0.6320018
cor(x = mat_dist, cophenetic(hc_cn))
[1] 0.7774966

El linkage single es el peor.

datos_clust$jer_complete <- as.factor(datos_clust$jer_cn)
p1 <- ggplot(datos_clust, aes(x=Elevation, y=Aspect, color=jer_cn)) +
  geom_point() + scale_color_brewer(palette="Dark2")

p2 <- ggplot(datos_clust, aes(x=Elevation, y=Slope, color=jer_cn)) +
  geom_point()
p2 + scale_color_brewer(palette="Dark2")

p3 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Hydrology, color=jer_cn)) +
  geom_point()
p3 + scale_color_brewer(palette="Dark2")

p4 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Roadways, color=jer_cn)) +
  geom_point()
p4 + scale_color_brewer(palette="Dark2")
library(ggplot2)
library(easyGgplot2)

datos_clust$jer_complete <- as.factor(datos_clust$jer_cn)

p2 <- ggplot(datos_clust, aes(x=Elevation, y=Slope, color=jer_cn, alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")

p3 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Hydrology, color=jer_cn,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none") +labs(y='H_Hydrology')


p4 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')

p7 <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')

p9 <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')

p10 <- ggplot(datos_clust, aes(x=Horizontal_Distance_To_Hydrology, y=Horizontal_Distance_To_Roadways, color=jer_cn,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(x='H_Hydrology',y='H_Roadways')

ggplot2.multiplot(p2,p3,p4,p7,p9,p10,cols=3)

png('jerarquical.png')

ggplot2.multiplot(p2,p3,p4,p7,p9,p10,cols=3)

dev.off()
png 
  2 

library(ggplot2)

p <- ggplot(datos_clust, aes(x=Aspect, y=Slope, color=jer_cn)) +
  geom_point()
p + scale_color_brewer(palette="Dark2")

p <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Hydrology, color=jer_cn)) +
  geom_point()
p + scale_color_brewer(palette="Dark2")

p <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Roadways, color=jer_cn)) +
  geom_point()
p + scale_color_brewer(palette="Dark2")


p <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Hydrology, color=jer_cn)) +
  geom_point()
p + scale_color_brewer(palette="Dark2")
library(ggplot2)

p <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Roadways, color=jer_cn)) +
  geom_point()
p + scale_color_brewer(palette="Dark2")

p <- ggplot(datos_clust, aes(x=Horizontal_Distance_To_Hydrology, y=Horizontal_Distance_To_Roadways, color=jer_complete)) +
  geom_point()
p + scale_color_brewer(palette="Dark2")

Solo lo hice con el primer metodo de cluster jerarquico que fue el que mejor dio el coeficiente cofrenetico.

library(cluster)

datos_kmeans = datos_clust[1:5]

cantidad_clusters=7

CL  = kmeans(scale(datos_kmeans),cantidad_clusters)
datos_kmeans$kmeans = CL$cluster
library(ggplot2)
library(easyGgplot2)

datos_kmeans$kmeans <- as.factor(datos_kmeans$kmeans)

p1 <- ggplot(datos_clust, aes(x=Elevation, y=Aspect, color=datos_kmeans$kmeans, alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")

p2 <- ggplot(datos_clust, aes(x=Elevation, y=Slope, color=datos_kmeans$kmeans, alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")

p3 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Hydrology, color=datos_kmeans$kmeans,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none") +labs(y='H_Hydrology')

p4 <- ggplot(datos_clust, aes(x=Elevation, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')

p5 <- ggplot(datos_clust, aes(x=Aspect, y=Slope, color=datos_kmeans$kmeans, alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none")

p6 <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Hydrology, color=datos_kmeans$kmeans, alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2") + theme(legend.position = "none") +labs(y='H_Hydrology')

p7 <- ggplot(datos_clust, aes(x=Aspect, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')

p8 <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Hydrology, color=datos_kmeans$kmeans,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')

p9 <- ggplot(datos_clust, aes(x=Slope, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(y='H_Roadways')

p10 <- ggplot(datos_clust, aes(x=Horizontal_Distance_To_Hydrology, y=Horizontal_Distance_To_Roadways, color=datos_kmeans$kmeans,alpha=0.5)) +
  geom_point() + scale_color_brewer(palette="Dark2")+ theme(legend.position = "none")+labs(x='H_Hydrology',y='H_Roadways')

ggplot2.multiplot(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,cols=3)

png('jerarquical_2.png')

ggplot2.multiplot(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,cols=3)

dev.off()
png 
  2 

LS0tCnRpdGxlOiAiVHJhYmFqbyBQcsOhY3RpY28gQUlEIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCmBgYHtyIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89RkFMU0UpCmBgYAoKIyBEZXNjcmlwY2lvbiBiYXNlIGRlIGRhdG9zCgojIyMgVGl0dWxvIGRlIGxhIGJhc2UgZGUgZGF0b3M6CgpGb3Jlc3QgQ292ZXJ0eXBlIGRhdGEsIFJlbW90ZSBTZW5zaW5nIGFuZCBHSVMgUHJvZ3JhbSBEZXBhcnRtZW50IG9mIEZvcmVzdCBTY2llbmNlcyBDb2xsZWdlIG9mIE5hdHVyYWwgUmVzb3VyY2VzIENvbG9yYWRvIFN0YXRlIFVuaXZlcnNpdHkuCgpFbCBkYXRhc2V0IGNvbnNpc3RlIGVuIG1lZGlkYXMgY2FydG9ncmFmaWNhcyBkZSBhcmVhcyBzaWx2ZXN0cmVzIGVuIGVsIEJvc3F1ZSBOYWNpb25hbCBSb29zZXZlbHQgZW4gZWwgCm5vcnRlIGRlIENvbG9yYWRvIGVuIFVTQSB5IGVsIHRpcG8gZGUgY292ZXJ0dXJhIHZlZ2V0YWwgcHJlc2VudGUuIFNlIHJlYWxpemFuIGVuIGNlbGRhcyAgZGUgMzAgWCAzMCBtZXRyb3MuIApFc3RvcyBib3NxdWVzIHRpZW5lbiBwb2NhIGludGVydmVuY2lvbiAKaHVtYW5hLCBwb3IgZW5kZSBsYXMgYXJlYXMgY2FyYWN0ZXJpemFuIG5vIHVzb3MgKGNvbW8gYWdyaWN1bHR1cmEsIHBvYmxhY2lvbiBldGMpIHNpbm8gY2xhc2UgZGUgYm9zcXVlIApwcmVkb21pbmFudGUuIEVuIHRvdGFsIHNvbiAxMiBtZWRpZGFzIGNhcnRvZ3JhZmljYXMgeSA3IGdyYW5kZXMgdGlwb3MgZGUgY292ZXJ0dXJhIHZlZ2V0YWwuCgpFbCBkYXRhc2V0OgoJCk51bWVybyBkZSBjb2x1bW5hczoKYGBge3J9Cmxlbmd0aChkZikKYGBgCgpOdW1lcm8gZGUgb2JzZXJ2YWNpb25lczogIApgYGB7cn0KCm5yb3coZGYpCmBgYAojIyMgQXRyaWJ1dG9zOgoKYGBge3J9Cm5hbWVzKGRmWyxjKDE6MTAsNTUpXSkKYGBgCk90cm9zIGF0cmlidXRvcyBzb24gY3VhdHJvIGFyZWFzIHNpbHZlc3RyZXMgeSA0MCB0aXBvcyBkZSBzdWVsby4KCkVsIHRpcG8gZGUgYXRyaWJ1dG8sIGxhcyB1bmlkYWRlcyB5IHN1IGRlc2NyaXBjaW9uOgoKTmFtZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICBEYXRhIFR5cGUgICAgICAgfCAgICAgIE1lYXN1cmVtZW50ICAgICAgICB8ICAgICAgICAgICAgRGVzY3JpcHRpb24KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpFbGV2YXRpb24gICAgICAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICBtZXRlcnMgICAgICAgICAgICAgICAgIHwgIEVsZXZhdGlvbiBpbiBtZXRlcnMKQXNwZWN0ICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgICBxdWFudGl0YXRpdmUgICAgfCAgYXppbXV0aCAgICAgICAgICAgICAgICB8ICBBc3BlY3QgaW4gZGVncmVlcyBhemltdXRoClNsb3BlICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgcXVhbnRpdGF0aXZlICAgIHwgIGRlZ3JlZXMgICAgICAgICAgICAgICAgfCAgU2xvcGUgaW4gZGVncmVlcwpIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICBtZXRlcnMgICAgICAgICAgICAgICAgIHwgIEhvcnogRGlzdCB0byBuZWFyZXN0IHN1cmZhY2Ugd2F0ZXIgZmVhdHVyZXMKVmVydGljYWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5ICAgIHwgICBxdWFudGl0YXRpdmUgICAgfCAgbWV0ZXJzICAgICAgICAgICAgICAgICB8ICBWZXJ0IERpc3QgdG8gbmVhcmVzdCBzdXJmYWNlIHdhdGVyIGZlYXR1cmVzCkhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMgICB8ICAgcXVhbnRpdGF0aXZlICAgIHwgIG1ldGVycyAgICAgICAgICAgICAgICAgfCAgSG9yeiBEaXN0IHRvIG5lYXJlc3Qgcm9hZHdheQpIaWxsc2hhZGVfOWFtICAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICAwIHRvIDI1NSBpbmRleCAgICAgICAgIHwgIEhpbGxzaGFkZSBpbmRleCBhdCA5YW0sIHN1bW1lciBzb2xzdGljZQpIaWxsc2hhZGVfTm9vbiAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICAwIHRvIDI1NSBpbmRleCAgICAgICAgIHwgIEhpbGxzaGFkZSBpbmRleCBhdCBub29uLCBzdW1tZXIgc29sdGljZQpIaWxsc2hhZGVfM3BtICAgICAgICAgICAgICAgICAgICAgfCAgIHF1YW50aXRhdGl2ZSAgICB8ICAwIHRvIDI1NSBpbmRleCAgICAgICAgIHwgIEhpbGxzaGFkZSBpbmRleCBhdCAzcG0sIHN1bW1lciBzb2xzdGljZQpIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0ZpcmVfUG9pbnRzfCAgIHF1YW50aXRhdGl2ZSAgICB8ICBtZXRlcnMgICAgICAgICAgICAgICAgIHwgIEhvcnogRGlzdCB0byBuZWFyZXN0IHdpbGRmaXJlIGlnbml0aW9uIHBvaW50cwpXaWxkZXJuZXNzX0FyZWEgKDQgYmluYXJ5IGNvbHVtbnMpfCAgIHF1YWxpdGF0aXZlICAgICB8ICAwIChhYnNlbmNlKSBvciAxIChwcmVzZW5jZSl8ICBXaWxkZXJuZXNzIGFyZWEgZGVzaWduYXRpb24KU29pbF9UeXBlICg0MCBiaW5hcnkgY29sdW1ucykgICAgIHwgICBxdWFsaXRhdGl2ZSAgICAgfCAgMCAoYWJzZW5jZSkgb3IgMSAocHJlc2VuY2UgfCAgU29pbCBUeXBlIGRlc2lnbmF0aW9uCkNvdmVyX1R5cGUgKDcgdHlwZXMpICAgICAgICAgICAgICB8ICAgaW50ZWdlciAgICAgICAgIHwgIDEgdG8gNyAgICAgICAgICAgICAgICAgICAgIHwgIEZvcmVzdCBDb3ZlciBUeXBlIGRlc2lnbmF0aW9uCgpMYXMgY3VhdHJvIGFyZWFzIHNpbHZlc3RyZXM6ICAJCgoqIFJhd2FoIFdpbGRlcm5lc3MgQXJlYQoqIE5lb3RhIFdpbGRlcm5lc3MgQXJlYQoqIENvbWFuY2hlIFBlYWsgV2lsZGVybmVzcyBBcmVhCiogQ2FjaGUgbGEgUG91ZHJlIFdpbGRlcm5lc3MgQXJlYQoKClRpcG8gZGUgY292ZXJ0dXJhIGZvcmVzdGFsOgkKCiogU3BydWNlL0ZpciAocGljZWEgLXBpbm8tL2FiZXRvKQoqIExvZGdlcG9sZSBQaW5lIChwaW5vKQoqIFBvbmRlcm9zYSBQaW5lIChwaW5vKQoqIENvdHRvbndvb2QvV2lsbG93IChzYXVjZXMpCiogQXNwZW4gKGFsYW1vcykKKiBEb3VnbGFzLWZpciAocGlubyBvcmVnb24pCiogS3J1bW1ob2x6ICh2ZWdldGFjacOzbiBhdHJvZmlhZGEgeSBkZWZvcm1hZGEsIGFyYm9sICJiYW5kZXJhIikKCiMjIyBNaXNzaW5ncwoKTm8gaGF5IG1pc3NpbmdzLgoKYGBge3J9CmNvbFN1bXMoaXMubmEoZGYpKQpgYGAKIyMjVmFyaWFibGVzIGNhdGVnb3JpY2FzIyMjCgpVbmEgZGUgbGFzIHZhcmlhYmxlcyBjb25zaXN0ZSBlbiBtZWRpZGFzIGRlbCB0aXBvIGRlIHN1ZWxvIHByZXNlbnRlLiBFbiBlbCBkYXRhc2V0IGVzdGEgdmFyaWFibGUgc2UgbWFwZW8gYSA0MCBvbmUtaG90IHZhcmlhYmxlcy4gQ2FkYSB2YXJpYWJsZSBjb3JyZXNwb25kZSBhIHVuIHRpcG8gZGUgc3VlbG8sIGNhdGFsb2dhZG8gY29uIHVuIGNvZGlnbyBxdWUgaW5jbHV5ZSBpbmZvcm1hY2lvbiBkZSBsYSB6b25hIGNsaW1hdGljYSB5IHpvbmEgZ2VvbG9naWNhLiBMYSBkaXN0cmlidWNpb24gZGUgZXN0YSB2YXJpYWJsZSBlcyBsYSBzaWd1aWVudGUgOgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCgpkZl9zdWVsb3MgPC0gZGZbLDE1OjU0XQoKZm9yIChqIGluIDE6bGVuZ3RoKGRmX3N1ZWxvcykpewogICAgZm9yIChpIGluIDE6bnJvdyhkZl9zdWVsb3MpKXsKICAgICAgICBpZiAoZGZfc3VlbG9zW2ksal09PTEpewogICAgICAgICAgICBkZl9zdWVsb3NbaSw0MV08LXBhc3RlKCJTb2lsX1R5cGUiLGosc2VwID0gIiIpCiAgICAgICAgfQogICAgfQp9CmRmX3N1ZWxvcyA8LSByZW5hbWUoZGZfc3VlbG9zLCBzdWVsbz1WNDEpCgpiYXJwbG90KHNvcnQodGFibGUoZGZfc3VlbG9zJHN1ZWxvKSksbGFzPTIsIGxvZz0ieSIsIHhsYWIgPSAic3VlbG8iLCBjb2wgPSAiIzFiOThlMCIpCnBuZygnYmFyc3VlbG8ucG5nJykKYmFycGxvdChzb3J0KHRhYmxlKGRmX3N1ZWxvcyRzdWVsbykpLGxhcz0yLCBsb2c9InkiLCB4bGFiID0gInN1ZWxvIiwgY29sID0gIiMxYjk4ZTAiKQoKZGV2Lm9mZigpCgpgYGAKCkVsIHN1ZWxvIG1hcyBhYnVuZGFudGUgdGllbmUgZWwgZG9ibGUgZGUgZnJlY3VlbmNpYSBxdWUgZWwgc2lndWllbnRlIG1hcyBhYnVuZGFudGUgeSBzdSBmcmVjdWVuY2lhIGVzdGEgNCBvcmRlbmVzIGRlIG1hZ25pdHVkIHBvciBlbmNpbWEgZGVsIHN1ZWxvIG1lbm9zIGZyZWN1ZW50ZS4gRW4gdmlzdGEgZGUgcXVlIHNvbiBtdWNoYXMgdmFyaWFibGVzIGluZGl2aWR1YWxlcyBhIHNlciBjb25zaWRlcmFkYXMgeSBubyBwdWVkZW4gc2VyIGNvbWJpbmFkYXMgKG5vIHNpbiBjb25vY2ltaWVudG8gZXhwZXJ0byBlbiBnZW9sb2dpYSkgcGFyYSByZWR1Y2lyIHN1IG51bWVyby4gTm8gc2VyYSBjb25zaWRlcmFkYSBlbiBlbCBhbmFsaXNpcy4KCk90cmEgZGUgbGFzIHZhcmlhYmxlcyBjYXRlZ29yaWNhcyBlcyBsYSB6b25hIHNpbHZlc3RyZSBkZSBsYSBjZWxkYS4gTGEgYWJ1bmRhbmNpYSBkZSB6b25hczoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQoKZGZfYXJlYXMgPC0gZGZbLDExOjE0XQoKZm9yIChqIGluIDE6bGVuZ3RoKGRmX2FyZWFzKSl7CiAgICBmb3IgKGkgaW4gMTpucm93KGRmX2FyZWFzKSl7CiAgICAgICAgaWYgKGRmX2FyZWFzW2ksal09PTEpewogICAgICAgICAgICBkZl9hcmVhc1tpLDVdPC1wYXN0ZSgiQXJlYSIsaixzZXAgPSAiIikKICAgICAgICB9CiAgICB9Cn0KZGZfYXJlYXM8LSByZW5hbWUoZGZfYXJlYXMsIGFyZWFzPVY1KQoKYmFycGxvdChzb3J0KHRhYmxlKGRmX2FyZWFzJGFyZWFzKSksbGFzPTIsIHhsYWIgPSAiYXJlYXMgc2lsdmVzdHJlcyIsIGNvbCA9ICIjRjRBNTgyIikKCnBuZygnYmFyYXJlYXMucG5nJykKCmJhcnBsb3Qoc29ydCh0YWJsZShkZl9hcmVhcyRhcmVhcykpLGxhcz0yLCB4bGFiID0gImFyZWFzIHNpbHZlc3RyZXMiLCBjb2wgPSAiI0Y0QTU4MiIpCgpkZXYub2ZmKCkKYGBgCkxhcyBhcmVhcyBzaWx2ZXN0cmVzIDMgeSA0IHNvbiBhbHJlZGVkb3IgZGUgOCB2ZWNlcyBtYXMgYWJ1bmRhbnRlcyBlbiBlbCBkYXRhc2V0IHF1ZSBsYXMgMSB5IDIuIFRhbWJpZW4gc2UgcG9kcmlhIHVzYXIgcGFyYSBjbGFzaWZpY2FyIHBlcm8gc2UgZXNjb2dlIGxhIGNsYXNlIGRlIGNvYmVydHVyYSBmb3Jlc3RhbC4KCkRlc2NyaXBjaW9uIGRlIGxhIHZhcmlhYmxlIG9iamV0aXZvLCBsYXMgY2xhc2VzIGRlIGNvYmVydHVyYSB2ZWdldGFsOgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShwbG90bHkpCgpsYWJlbHMgPSBjKCdTcHJ1Y2UvRmlyJywnTG9kZ2Vwb2xlIFBpbmUnLCdQb25kZXJvc2EgUGluZScsJ0NvdHRvbndvb2QvV2lsbG93JywnQXNwZW4nLCdEb3VnbGFzLWZpcicsCiAgICAgICAgICAnS3J1bW1ob2x6JykKdmFsdWVzIDwtIGFnZ3JlZ2F0ZShkZiRFbGV2YXRpb25+IGRmJEZvcmVzdF9Db3Zlcl9UeXBlLCBkYXRhPWRmLCBGVU49bGVuZ3RoKQpmaWcgPC0gcGxvdF9seSh0eXBlPSdwaWUnLCBsYWJlbHM9bGFiZWxzLCB2YWx1ZXM9dmFsdWVzJGBkZiRFbGV2YXRpb25gLCAKICAgICAgICAgICAgICAgdGV4dGluZm89J2xhYmVsK3BlcmNlbnQnLAogICAgICAgICAgICAgICBpbnNpZGV0ZXh0b3JpZW50YXRpb249J3JhZGlhbCcsc2hvd2xlZ2VuZCA9IEZBTFNFKQpmaWcKCiNwbmcoJ2NvdmVydHlwZS5wbmcnKQoKI2ZpZwoKI2Rldi5vZmYoKQoKI3BpZSh0YWJsZShkZiRGb3Jlc3RfQ292ZXJfVHlwZSksbWFpbj0nVGlwb3MgZGUgY292ZXJ0dXJhJykKI2JhcnBsb3QodGFibGUoZGYkV29ybGQucmVnaW9uKSwgeGxhYiA9ICJSZWdpb24iLCB5bGFiID0gIkNhbnRpZGFkIGRlIENpdWRhZGVzIiwgICAgICAgICBtYWluPSJDaXVkYWRlcyBwb3IgcmVnaW9uIixjZXgubmFtZXMgPSAuMywgbGFzPTIpIApgYGAKClNlIHB1ZWRlIG9ic2VydmFyIHF1ZSBsb3MgZGF0b3MgZXN0YW4gYmFzdGFudGUgZGVzYmFsYW5jZWFkb3MuIEVzdG8gaW5jaWRpcsOtYSBlbiB1bmEgcmVncmVzacOzbiBwZXJvIG5vIGVuIHVuIExEQS4KClJlc3VtZW4gZGUgbG9zIGRhdG9zOgoKYGBge3J9CmRmX21heDwtYXBwbHkoZGZbLDE6MTBdLDIsbWF4KQpkZl9taW48LWFwcGx5KGRmWywxOjEwXSwyLG1pbikKZGZfbiA8LSBzd2VlcChkZlssMToxMF0sIDIsIGRmX21pbiwgIi0iKQpyYW5nZSA8LSBkZl9tYXgtZGZfbWluCmRmX24gPC0gc3dlZXAoZGZfbiwgMiwgcmFuZ2UsICIvIikKYm94cGxvdChkZl9uLGxhcz0yKQpgYGAKClRvZGFzIGxhcyBkaXN0cmlidWNpb25lcyBzb24gc2VzZ2FkYXMgeSB0aWVuZW4gbXVjaG9zIG91dGxpZXJzLiBQYXJhIGNsYXNpZmljYXIsIHNpIHNlIHZhIGEgYXBsaWNhciB1biBMREEsIGhheSBxdWUgZ2FyYW50aXphciBub3JtYWxpZGFkIHkgcXVlIGxhcyBtZWRpYXMgc2VhbiBkaXN0aW50YXMsIHBlcm8gc2UgdmVyYSBtYXMgYWRlbGFudGUuIFRhbWJpZW4gZXMgaW1wb3J0YW50ZSBnYXJhbnRpemFyIHF1ZSBsb3MgYXRyaWJ1dG9zIHNlYW4gaW5kZXBlbmRpZW50ZXMuCgpMYSB2YXJpYWJsZSBBc3BlY3QgZXMgYmltb2RhbCB5IHByb2JhYmxlbWVudGUgU2xvcGUgZXMgbXVsdGltb2RhbC4gVG9kYXMgbGFzIHZhcmlhYmxlcyB0aWVuZW4gY29sYXMgbGFyZ2FzLCBleGNlcHRvIEhpbGxzaGFkZV8zcG0uIEVuIHRvZGFzIGxhcyB2YXJpYWJsZXMgZXhjZXB0byBlbiBFbGV2YXRpb24gbGFzIGNsYXNlcyBzaWd1ZW4gbGEgbWlzbWEgdGVuZGVuY2lhLiBFbiBlc3RhIHZhcmlhYmxlIGxhcyBkaXN0cmlidWNpb25lcyBwYXJhIGNhZGEgZ3J1cG8gZXN0YW4gY2VudHJhZGFzIGRpc3RpbnRvOgoKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShlYXN5R2dwbG90MikKCgpzZXQuc2VlZCg0MikKCnJvd3MgPC0gc2FtcGxlKG5yb3coZGYpKQpkZl9yYW5kIDwtIGRmW3Jvd3MsIF0KZGZfbmV3IDwtIGRmX3JhbmRbc2VxKDEsNTAwMDAwLDEwMCksYygxOjEwKV0KY292ZXJfdHlwZSA8LSBkZl9yYW5kW3NlcSgxLDUwMDAwMCwxMDApLDU1XQpzdWJzZXQwIDwtIGNiaW5kKGRmX25ldyxjb3Zlcl90eXBlKQpzdWJzZXQwJGNvdmVyX3R5cGUgPC0gYXMuZmFjdG9yKHN1YnNldDAkY292ZXJfdHlwZSkKCiMgSGlzdG9ncmFtYSBFbGV2YXRpb24KcGxvdDEgPC0gZ2dwbG90Mi5oaXN0b2dyYW0oZGF0YT1zdWJzZXQwLCB4TmFtZT0nRWxldmF0aW9uJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJywgbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgRWxldmF0aW9uIiwgeHRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHl0aXRsZUZvbnQ9YygxMCwiYm9sZCIsICJibGFjayIpLCB4VGlja0xhYmVsRm9udD1jKDcsICJib2xkIiwgImJsYWNrIiksIHlUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeHRpdGxlID0gIkVsZXZhdGlvbiIsIHl0aXRsZSA9ICJGcmVjdWVuY2lhIiwgYmFja2dyb3VuZENvbG9yPSJ3aGl0ZSIsIGJyZXdlclBhbGV0dGU9IlBhaXJlZCIsYWxwaGE9MC41LHNob3dMZWdlbmQ9RkFMU0UscG9zaXRpb249InN0YWNrIikKIyBIaXN0b2dyYW1hIEFzcGVjdApwbG90MiA8LSBnZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdBc3BlY3QnLGdyb3VwTmFtZT0nY292ZXJfdHlwZScsIG1haW50aXRsZSA9ICJIaXN0b2dyYW1hIEFzcGVjdCIsIHh0aXRsZUZvbnQ9YygxMCwiYm9sZCIsICJibGFjayIpLCB5dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeFRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB5VGlja0xhYmVsRm9udD1jKDcsICJib2xkIiwgImJsYWNrIiksIHh0aXRsZSA9ICJBc3BlY3QiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBTbG9wZQpwbG90MyA8LSBnZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdTbG9wZScsIGdyb3VwTmFtZT0nY292ZXJfdHlwZScsbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgU2xvcGUiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiU2xvcGUiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoIGh5ZHJvCnBsb3Q0IDwtZ2dwbG90Mi5oaXN0b2dyYW0oZGF0YT1zdWJzZXQwLCB4TmFtZT0nSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3knLCBncm91cE5hbWU9J2NvdmVyX3R5cGUnLG1haW50aXRsZSA9ICJIaXN0b2dyYW1hIEhvcml6LiBkaXN0YW5jZSB0byBoeWRyb2xvZ3kiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSF9oeWRyb2xvZ3kiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSB2IGh5ZHJvCnBsb3Q1IDwtZ2dwbG90Mi5oaXN0b2dyYW0oZGF0YT1zdWJzZXQwLCB4TmFtZT0nVmVydGljYWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5JywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJyxtYWludGl0bGUgPSAiSGlzdG9ncmFtYSBWZXJ0LiBkaXN0YW5jZSB0byBoeWRyb2xvZ3kiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiVl9oeWRyb2xvZ3kiLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoIHJvYWQKcGxvdDYgPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJyxtYWludGl0bGUgPSAiSGlzdG9ncmFtYSBIb3Jpei4gZGlzdGFuY2UgdG8gcm9hZHdheXMiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSF9yb2Fkd2F5cyIsIHl0aXRsZSA9ICJGcmVjdWVuY2lhIiwgYmFja2dyb3VuZENvbG9yPSJ3aGl0ZSIsIGJyZXdlclBhbGV0dGU9IlBhaXJlZCIsYWxwaGE9MC41LHNob3dMZWdlbmQ9RkFMU0UscG9zaXRpb249InN0YWNrIikKIyBIaXN0b2dyYW1hIGhpbGwgOQpwbG90NyA8LWdncGxvdDIuaGlzdG9ncmFtKGRhdGE9c3Vic2V0MCwgeE5hbWU9J0hpbGxzaGFkZV85YW0nLCBncm91cE5hbWU9J2NvdmVyX3R5cGUnLG1haW50aXRsZSA9ICJIaXN0b2dyYW1hIEhpbGxzaGFkZV85YW0iLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSGlsbHNoYWRlXzlhbSIsIHl0aXRsZSA9ICJGcmVjdWVuY2lhIiwgYmFja2dyb3VuZENvbG9yPSJ3aGl0ZSIsIGJyZXdlclBhbGV0dGU9IlBhaXJlZCIsYWxwaGE9MC41LHNob3dMZWdlbmQ9RkFMU0UscG9zaXRpb249InN0YWNrIikKIyBIaXN0b2dyYW1hIGhpbGwgMTIKcGxvdDggPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIaWxsc2hhZGVfTm9vbicsIGdyb3VwTmFtZT0nY292ZXJfdHlwZScsbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgSGlsbHNoYWRlX25vb24iLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSGlsbHNoYWRlX25vb24iLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoaWxsIDMKcGxvdDkgPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIaWxsc2hhZGVfM3BtJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJyxtYWludGl0bGUgPSAiSGlzdG9ncmFtYSBIaWxsc2hhZGVfM3BtIiwgeHRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHl0aXRsZUZvbnQ9YygxMCwiYm9sZCIsICJibGFjayIpLCB4VGlja0xhYmVsRm9udD1jKDcsICJib2xkIiwgImJsYWNrIiksIHlUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeHRpdGxlID0gIkhpbGxzaGFkZV8zcG0iLCB5dGl0bGUgPSAiRnJlY3VlbmNpYSIsIGJhY2tncm91bmRDb2xvcj0id2hpdGUiLCBicmV3ZXJQYWxldHRlPSJQYWlyZWQiLGFscGhhPTAuNSxzaG93TGVnZW5kPUZBTFNFLHBvc2l0aW9uPSJzdGFjayIpCiMgSGlzdG9ncmFtYSBoIGZpcmVwb2ludApwbG90MTAgPC1nZ3Bsb3QyLmhpc3RvZ3JhbShkYXRhPXN1YnNldDAsIHhOYW1lPSdIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0ZpcmVfUG9pbnRzJywgZ3JvdXBOYW1lPSdjb3Zlcl90eXBlJywgbWFpbnRpdGxlID0gIkhpc3RvZ3JhbWEgSG9yaXouIGRpc3RhbmNlIHRvIGZpcmVwb2ludHMiLCB4dGl0bGVGb250PWMoMTAsImJvbGQiLCAiYmxhY2siKSwgeXRpdGxlRm9udD1jKDEwLCJib2xkIiwgImJsYWNrIiksIHhUaWNrTGFiZWxGb250PWMoNywgImJvbGQiLCAiYmxhY2siKSwgeVRpY2tMYWJlbEZvbnQ9Yyg3LCAiYm9sZCIsICJibGFjayIpLCB4dGl0bGUgPSAiSF9maXJlcG9pbnRzIiwgeXRpdGxlID0gIkZyZWN1ZW5jaWEiLCBiYWNrZ3JvdW5kQ29sb3I9IndoaXRlIiwgYnJld2VyUGFsZXR0ZT0iUGFpcmVkIixhbHBoYT0wLjUsc2hvd0xlZ2VuZD1GQUxTRSxwb3NpdGlvbj0ic3RhY2siKQojCgpnZ3Bsb3QyLm11bHRpcGxvdChwbG90MSxwbG90MixwbG90MyxwbG90NCxwbG90NSxwbG90NixwbG90NyxwbG90OCxwbG90OSxwbG90MTAsIGNvbHM9NCkKCnBuZygnaGlzdG9ncmFtcy5wbmcnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocGxvdDEscGxvdDIscGxvdDMscGxvdDQscGxvdDUscGxvdDYscGxvdDcscGxvdDgscGxvdDkscGxvdDEwLCBjb2xzPTMpCgpkZXYub2ZmKCkKYGBgCgojIyMgQ29ycmVsYWNpb25lcwoKCmBgYHtyfQpsaWJyYXJ5KGdnY29ycnBsb3QpCgpjb3JyIDwtIHJvdW5kKGNvcihzdWJzZXQwWywxOjEwXSksIDEpCnAubWF0PC0gY29yX3BtYXQoc3Vic2V0MFssMToxMF0pCgpnZ2NvcnJwbG90KGNvcnIsIGxhYiA9IFRSVUUsCiAgICAgb3V0bGluZS5jb2wgPSAid2hpdGUiLGxhYl9zaXplID0gMywgdGwuY2V4PTUsbWV0aG9kID0gImNpcmNsZSIsaGMub3JkZXIgPSBUUlVFLCBwLm1hdCA9IHAubWF0KQoKcG5nKCdjb3JyZWxhdGlvbi5wbmcnKQoKZ2djb3JycGxvdChjb3JyLCBsYWIgPSBUUlVFLAogICAgIG91dGxpbmUuY29sID0gIndoaXRlIixsYWJfc2l6ZSA9IDMsIHRsLmNleD01LCBtZXRob2QgPSAiY2lyY2xlIixoYy5vcmRlciA9IFRSVUUsIHAubWF0ID0gcC5tYXQpCgpkZXYub2ZmKCkKCmBgYAoKTGFzIHZhcmlhYmxlcyBoaWxsc2hhZGUgZXN0YW4gcmVsYWNpb25hZGFzIGNvbiBhc3BlY3QsIHNsb3BlIHkgZW50cmUgZWxsYXMuIEVzdG8gdGllbmUgcXVlIHZlciBjb24gY29tbyBzZSBjYWxjdWxhbiBsYXMgY2FudGlkYWRlcyBoaWxsc2hhZGUgcXVlIHNvbiBsb3Mgc29tYnJlYWRvcyBxdWUgc2UgZGlidWphbiBzb2JyZSBsb3MgbWFwYXMgY2FydG9ncmFmaWNvcy4gU2Ugc3Vwb25lIHVuYSBpbHVtaW5hY2lvbiBzaW11bGFkYSBxdWUgZGVwZW5kZSBkZSBsYSBvcmllbnRhY2lvbiBhIGxhIGZ1ZW50ZSBkZSBsdXosIGxhIGN1YWwgZXN0YSBiYXNhZGEgZW4gbGFzIHZhcmlhYmxlcyBhc3BlY3QgeSBzbG9wZS4gTGFzIGNhbnRpZGFkZXMgZGlzdGFuY2lhIHZlcnRpY2FsIHkgaG9yaXpvbnRhbCBhIGN1cnNvcyBkZSBhZ3VhIHRhbWJpZW4gZXN0YW4gcmVsYWNpb25hZGFzIHkgc2UgY2FsY3VsYW4gY29uIHNsb3BlIHkgZWxldmF0aW9uLiBMYSBkaXN0YW5jaWEgaG9yaXpvbnRhbCBhIGNhbWlub3MgeSBhIHB1bnRvcyBkZSBmdWVnbyB0YW1iaWVuIHNlIGNhbGN1bGFuIGNvbiBsYSBlbGV2YWNpb24geSBlc3RhbiByZWxhY2lvbmFkb3MgZW50cmUgc2kuCgpDb24gYmFzZSBlbiBlc3RvIGFjb3RvIGxhcyB2YXJpYWJsZXMgYSBFbGV2YXRpb24sIEFzcGVjdCwgU2xvcGUsIEhpbGxzaGFkZV9ub29uLCBIb3Jpem9udGFsX0Rpc3RhbmNlX1JvYWR3YXlzIHkgSG9yaXpvbnRhbF9EaXN0YW5jZV90b19IeWRyb2xvZ3kuCgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9CmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KGRhdGEudGFibGUpCgpjb2xvciA8LSBhcy5mYWN0b3Ioc3Vic2V0Wyw3XSkKZ2dwYWlycyhzdWJzZXRbLDE6NV0sIGFlcyhjb2xvciA9IGNvbG9yLCBhbHBoYSA9IDAuNSksbG93ZXIgPSBsaXN0KGNvbWJvID0gImNvdW50IiksdXBwZXIgPSAiYmxhbmsiLCkKYGBgCgpTZSBwdWVkZW4gb2JzZXJ2YXIgdGFtYmllbiBsYXMgZGlzdHJpYnVjaW9uZXMgZGUgbGFzIGNsYXNlcywgc29uIHNlc2dhZGFzIGNvbW8gc2UgdmlvIGVuIGxvcyBib3hwbG90cy4gTGEgdmFyaWFibGUgcXVlIHBvZHJpYSBzZXJ2aXIgbWFzIHBhcmEgZGlzdGluZ3VpciBjbGFzZXMgcG9kcmlhIHNlciBFbGV2YXRpb24uCgoKYGBge3J9CnN1YnN1YnNldCA8LSBzdWJzZXRbLGMoMTo0LDYsOCwxMSldCmBgYAoKCiMjIyBBbmFsaXNpcyBleHBsb3RhcmlvIGNvbiBQQ0EKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShnZ2JpcGxvdCkKbGlicmFyeShwbHlyKQoKZGF0b3NfcGNhIDwtIHByY29tcChzdWJzZXRbLDE6NSw3XSxzY2FsZT1UUlVFKQpwIDwtIGdnYmlwbG90KGRhdG9zX3BjYSwgb2JzLnNjYWxlPTAuMDEsYWxwaGEgPSAwLjMsZ3JvdXBzPXN1YnNldCRjb3Zlcl90eXBlKQpwIDwtIHAgKyB4bGltKC0zLCAyKSArIHlsaW0oLTIsIDMpCgpwbG90KHApCgpwbmcoJ3BjYS5wbmcnKQoKcGxvdChwKQoKZGV2Lm9mZigpCgpgYGAKCgojIENsYXNpZmljYWNpb24gU3VwZXJ2aXNhZGEKCiMjIExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMKCkVsIHByaW1lciBtZXRvZG8gYSBpbXBsZW1lbnRhciBlcyBMREEuIFBhcmEgcXVlIHRlbmdhIHZhbGlkZXogZWwgbWV0b2RvIGRlYmVuIGN1bXBsaXJzZSBsb3Mgc3VwdWVzdG9zIGRlIG5vcm1hbGlkYWQgbXVsdGl2YXJpYWRhIHBhcmEgY2FkYSBuaXZlbCBkZSBjYWRhIHZhcmlhYmxlIHkgZGUgaG9tb2NlZGFzdGljaWRhZC4KClBhcmEgZWwgdGVzdCBkZSBub3JtYWxpZGFkIG11bHRpdmFyaWFkYSB1c2Ftb3MgdGVzdCBkZSBTaGFwaXJvOgoKYGBge3J9CmxpYnJhcnkobXZub3JtdGVzdCkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nMScsMTo2XSkpCnNoYXBpdGVzdCA8LSB0ZXN0JHAudmFsdWUKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nMicsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nMycsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNCcsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNScsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNicsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0W3N1YnN1YnNldCRjb3Zlcl90eXBlPT0nNycsMTo2XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKCnNoYXBpdGVzdApgYGAKUmVjaGF6YSBub3JtYWxpZGFkIHBhcmEgdG9kb3MgbG9zIG5pdmVsZXMgZGUgdG9kYXMgbGFzIHZhcmlhYmxlcy4gUGFyYSBlbCB0b3RhbDoKCmBgYHtyfQpsaWJyYXJ5KG12bm9ybXRlc3QpCm1zaGFwaXJvLnRlc3QodChzdWJzZXRbLDE6Nl0pKQpgYGAKClNpIGVsIG5pdmVsIGRlIHNpZ25pZmljYWNpb24gcG9yIGRlZmVjdG8gZXMgZGUgMC4wNSwgcmVjaGF6YSBsYSBub3JtYWxpZGFkIG11bHRpdmFyaWFkYS4gCgpFbCBzaWd1aWVudGUgc3VwdWVzdG8gZGUgaG9tb2NlZGFzdGljaWRhZCBsbyBldmFsdWFtb3MgY29uIGVsIHRlc3QgTSBkZSBCb3g6CgpgYGB7cn0KbGlicmFyeShiaW90b29scykKCmJveE0oZGF0YT1zdWJzdWJzZXRbLDE6Nl0sZ3JvdXBpbmc9c3Vic3Vic2V0Wyw3XSkKYGBgClJlY2hhemEgbGEgaG9tb2dlbmVpZGFkIGRlIHZhcmlhbnphcy4gU2UgdGVuZHJpYSBxdWUgcmVhbGl6YXIgdW4gTERBIHJvYnVzdG8gKGN1YWRyYXRpY28pLgoKSWd1YWwsIGEgcGVzYXIgZGUgaGFiZXIgcmVjaGF6YWRvIGxhIG5vcm1hbGlkYWQgaGFjZW1vcyBlbCB0ZXN0IGRlIEhvdGVsbGluZyBwYXJhIGRldGVybWluYXIgc2kgbGFzIG1lZGlhcyBkZSBsb3MgZ3J1cG9zIHNvbiBkaXN0aW50YXM6CgpgYGB7cn0KbGlicmFyeShIb3RlbGxpbmcpCgpmaXRQcm9kID0gaG90ZWxsaW5nLnRlc3QoLn4gY292ZXJfdHlwZSwgZGF0YSA9IHN1YnN1YnNldCkgCmZpdFByb2QKYGBgClBvciBlbmRlIHJlY2hhemFtb3MgcXVlIGxhcyBtZWRpYXMgc2VhbiBpZ3VhbGVzLiBFbCBjb3Zlcl90eXBlIGVzIGRpc2NyaW1pbmFudGUuCgojIExEQQoKSGFiaWVuZG8gZGV0ZXJtaW5hZG8gcXVlIGxhIHZhcmlhYmxlIGRlIGNsYXNlIGVzIGRpc2NyaW1pbmFudGUsIGNhbGN1bGFtb3MgZWwgbGRhOgoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fbGRhIDwtIGxkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+IEVsZXZhdGlvbiArIEFzcGVjdCArIFNsb3BlICsgSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3kgKyBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzICsgSGlsbHNoYWRlX05vb24sIGRhdGEgPSBzdWJzZXQpCgpgYGAKCkFob3JhIHNlIHBydWViYSBlbCBtb2RlbG86CgpgYGB7cn0KcHJlZGljY2lvbmVzIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGEsIG5ld2RhdGEgPSB0ZXN0ZW8sIG1ldGhvZCA9ICJwcmVkaWN0aXZlIikKdGFibGUoY2xhc2UsIHByZWRpY2Npb25lcyRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKTG9zIGVycm9yZXMgZGUgcHJlZGljY2lvbjoKCmBgYHtyfQp0cmFpbmluZ19lcnJvciA8LSBtZWFuKGNsYXNlICE9IHByZWRpY2Npb25lcyRjbGFzcykgKiAxMDAgCnRyYWluaW5nX2Vycm9yIApgYGAKRWwgZXJyb3IgZGEgYmFzdGFudGUgZ3JhbmRlLi4uCgpDb24gdW5hIHZhcmlhYmxlIG1hczoKCmBgYHtyfQpsaWJyYXJ5KGtsYVIpIAoKdGVzdGVvJGNsYXNlIDwtIGFzLmZhY3Rvcih0ZXN0ZW8kY2xhc2UpCgpwYXJ0aW1hdChjbGFzZSB+IC4sIGRhdGEgPSB0ZXN0ZW8sIG1ldGhvZCA9ICJsZGEiLCBjb2wuY29ycmVjdD1OQSwgY29sLndyb25nPU5BLHBsb3QubWF0cml4PUZBTFNFLGltYWdlLmNvbG9ycyA9cmFpbmJvdyg3KSwpCmBgYApTaW4gZXNhIHZhcmlhYmxlOgoKYGBge3J9CmxpYnJhcnkoa2xhUikgCgp0ZXN0ZW8kY2xhc2UgPC0gYXMuZmFjdG9yKHRlc3RlbyRjbGFzZSkKCnBhcnRpbWF0KGNsYXNlIH4gLiwgZGF0YSA9IHRlc3Rlb1ssYygxOjUsNyldLCBtZXRob2QgPSAibGRhIiwgY29sLmNvcnJlY3Q9TkEsIGNvbC53cm9uZz1OQSxwbG90Lm1hdHJpeD1GQUxTRSxpbWFnZS5jb2xvcnMgPXJhaW5ib3coNyksc2NhbGVkPVRSVUUpCgpwbmcoJ3BhcnRpcGxvdC5wbmcnKQoKcGFydGltYXQoY2xhc2UgfiAuLCBkYXRhID0gdGVzdGVvWyxjKDE6NSw3KV0sIG1ldGhvZCA9ICJsZGEiLCBjb2wuY29ycmVjdD1OQSwgY29sLndyb25nPU5BLHBsb3QubWF0cml4PUZBTFNFLGltYWdlLmNvbG9ycyA9cmFpbmJvdyg3KSxzY2FsZWQ9VFJVRSkKCmRldi5vZmYoKQoKYGBgCgpEaXNjcmltaW5hIG11eSBtYWwuIEFob3JhLCBzaSB0cmFuc2Zvcm1vIGxhcyB2YXJpYWJsZXMgY29uIHVuIGxvZzoKCmBgYHtyfQpsaWJyYXJ5KElEUG1pc2MpCgpkZl90cmFucyA8LSBsb2coc3Vic2V0WywxOjZdKQpzdWJzZXRfdHJhbnMgPC0gY2JpbmQoZGZfdHJhbnMsY292ZXJfdHlwZSkKc3Vic2V0X3QgPC0gTmFSVi5vbWl0KHN1YnN1YnNldF90cmFucykKc3Vic2V0X3QkY292ZXJfdHlwZSA8LSBhcy5mYWN0b3Ioc3Vic2V0X3QkY292ZXJfdHlwZSkKYGBgCgoKYGBge3J9CmxpYnJhcnkoTUFTUykKCm1vZGVsb19sZGFfdHJhbnMgPC0gbGRhKGZvcm11bGEgPSBjb3Zlcl90eXBlIH4uICwgZGF0YSA9IHN1YnNldF90KQoKYGBgCgpgYGB7cn0KbGlicmFyeShJRFBtaXNjKQpsaWJyYXJ5KGRwbHlyKQoKdGVzdF90cmFucyA8LSBsb2codGVzdCkKdGVzdF90IDwtIGNiaW5kKHRlc3RfdHJhbnMsY2xhc2UpCnRlc3RfdCA8LSBOYVJWLm9taXQodGVzdF90KQpjbGFzZV90IDwtdGVzdF90JGNsYXNlCmBgYAoKCmBgYHtyfQpwcmVkaWNjaW9uZXNfdCA8LSAgcHJlZGljdChvYmplY3QgPSBtb2RlbG9fbGRhX3RyYW5zLCBuZXdkYXRhID0gdGVzdF90KQp0YWJsZShjbGFzZV90LCBwcmVkaWNjaW9uZXNfdCRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKCmBgYHtyfQp0cmFpbmluZ19lcnJvcl90IDwtIG1lYW4oY2xhc2VfdCAhPSBwcmVkaWNjaW9uZXNfdCRjbGFzcykgKiAxMDAgCnRyYWluaW5nX2Vycm9yX3QgCmBgYApQZXNpbW8uCgpTaSBjb25zaWRlcm8gdW5hIHZhcmlhYmxlIG1hcywgc2luIHRyYW5zZm9ybWFyOgoKCmBgYHtyfQoKbGlicmFyeShNQVNTKQoKbW9kZWxvX2xkYV8yIDwtIGxkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+LiAsIGRhdGEgPSBzdWJzZXRfMikKCmBgYApgYGB7cn0KcHJlZGljY2lvbmVzXzIgPC0gIHByZWRpY3Qob2JqZWN0ID0gbW9kZWxvX2xkYV8yLCBuZXdkYXRhID0gdGVzdGVvXzIpCnRhYmxlKGNsYXNlLCBwcmVkaWNjaW9uZXNfMiRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKCmBgYHtyfQp0cmFpbmluZ19lcnJvcl8yIDwtIG1lYW4odGVzdGVvXzIkY2xhc2UgIT0gcHJlZGljY2lvbmVzXzIkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcl8yIApgYGAKQ29uc2lkZXJhciBsYXMgdmFyaWFibGVzIGNvbiBsb2dhcml0bW8gbyBjb25zaWRlcmFyIHVuYSB2YXJpYWJsZSBhZGljaW9uYWwgbm8gbWVqb3JvIGxhIHByZWRpY2Npb24uIFNlIGludGVudGFyYSBhaG9yYSBjb24gdW4gbGRhIHJvYnVzdG86CgoKCmBgYHtyfQoKbGlicmFyeShNQVNTKQoKbW9kZWxvX3FkYSA8LSBxZGEoZm9ybXVsYSA9IGNvdmVyX3R5cGUgfiAuLCBkYXRhID0gc3Vic2V0KQoKYGBgCgpgYGB7cn0KcHJlZGljY2lvbmVzX3FkYSA8LSAgcHJlZGljdChvYmplY3QgPSBtb2RlbG9fcWRhLCBuZXdkYXRhID0gdGVzdGVvLCBtZXRob2QgPSAicHJlZGljdGl2ZSIpCnRhYmxlKGNsYXNlLCBwcmVkaWNjaW9uZXNfcWRhJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYApgYGB7cn0KdHJhaW5pbmdfZXJyb3JfcWRhIDwtIG1lYW4oY2xhc2UgIT0gcHJlZGljY2lvbmVzX3FkYSRjbGFzcykgKiAxMDAgCnRyYWluaW5nX2Vycm9yX3FkYQpgYGAKRGEgcGVvci4uLgoKQ29uIGxhcyBjbGFzZXMgZXNjYWxhZGFzOgoKYGBge3J9CmRhdG9zX2VzY2FsYWRvcyA8LSBhcy5kYXRhLmZyYW1lKHNjYWxlKHN1YnNldFssMTo1XSkpCnN1YnNldF9lc2MgPC0gY2JpbmQoZGF0b3NfZXNjYWxhZG9zLGNvdmVyX3R5cGUpCnN1YnNldF9lc2MkY292ZXJfdHlwZSA8LSBhcy5mYWN0b3Ioc3Vic2V0X2VzYyRjb3Zlcl90eXBlKQpgYGAKCnRlc3QgZGUgU2hhcGlybyBwYXJhIGxvcyBkYXRvcyBlc2NhbGFkb3M6CgpgYGB7cn0Kc3Vic2V0X2VzYyRjb3Zlcl90eXBlIDwtIGFzLmZhY3RvcihzdWJzZXRfZXNjJGNvdmVyX3R5cGUpCmBgYAoKYGBge3J9CmxpYnJhcnkobXZub3JtdGVzdCkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic2V0X2VzY1tzdWJzZXRfZXNjJGNvdmVyX3R5cGU9PScxJywxOjVdKSkKc2hhcGl0ZXN0IDwtIHRlc3QkcC52YWx1ZQp0ZXN0IDwtIG1zaGFwaXJvLnRlc3QodChzdWJzZXRfZXNjW3N1YnNldF9lc2MkY292ZXJfdHlwZT09JzInLDE6NV0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0JHAudmFsdWUpCnRlc3QgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnNldF9lc2Nbc3Vic2V0X2VzYyRjb3Zlcl90eXBlPT0nMycsMTo1XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic2V0X2VzY1tzdWJzZXRfZXNjJGNvdmVyX3R5cGU9PSc0JywxOjVdKSkKc2hhcGl0ZXN0IDwtIGFwcGVuZChzaGFwaXRlc3QsdGVzdCRwLnZhbHVlKQp0ZXN0IDwtIG1zaGFwaXJvLnRlc3QodChzdWJzZXRfZXNjW3N1YnNldF9lc2MkY292ZXJfdHlwZT09JzUnLDE6NV0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0JHAudmFsdWUpCnRlc3QgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnNldF9lc2Nbc3Vic2V0X2VzYyRjb3Zlcl90eXBlPT0nNicsMTo1XSkpCnNoYXBpdGVzdCA8LSBhcHBlbmQoc2hhcGl0ZXN0LHRlc3QkcC52YWx1ZSkKdGVzdCA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic2V0X2VzY1tzdWJzZXRfZXNjJGNvdmVyX3R5cGU9PSc3JywxOjVdKSkKc2hhcGl0ZXN0IDwtIGFwcGVuZChzaGFwaXRlc3QsdGVzdCRwLnZhbHVlKQoKc2hhcGl0ZXN0CmBgYAoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fbGRhX2VzYyA8LSBsZGEoZm9ybXVsYSA9IGNvdmVyX3R5cGUgfi4sIGRhdGEgPSBzdWJzZXRfZXNjKQoKYGBgCgpgYGB7cn0KdGVzdF9lc2MgPC0gYXMuZGF0YS5mcmFtZShzY2FsZSh0ZXN0KSkKdGVzdF9lc2MgPC0gY2JpbmQodGVzdF9lc2MsY2xhc2UpCnRlc3RfZXNjJGNsYXNlIDwtIGFzLmZhY3Rvcih0ZXN0X2VzYyRjbGFzZSkKYGBgCgpgYGB7cn0KcHJlZGljY2lvbmVzX2VzYyA8LSAgcHJlZGljdChvYmplY3QgPSBtb2RlbG9fbGRhX2VzYywgbmV3ZGF0YSA9IHRlc3RfZXNjLCBtZXRob2QgPSAicHJlZGljdGl2ZSIpCnRhYmxlKGNsYXNlLCBwcmVkaWNjaW9uZXNfZXNjJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yX2VzYyA8LSBtZWFuKGNsYXNlICE9IHByZWRpY2Npb25lc19lc2MkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcl9lc2MKYGBgCkVsIG1lam9yIHJlc3VsdGFkbyBmdWUgZWwgaW5pY2lhbC4gRW4gZ2VuZXJhbCBzZSBwcmVkaWNlbiBtdXkgbWFsIGxhcyBjbGFzZXMgbWVub3MgYWJ1bmRhbnRlcy4gCgpFbCBxZGEgY29uIGxvcyBkYXRvcyBlc2NhbGFkb3M6CgpgYGB7cn0KCmxpYnJhcnkoTUFTUykKCm1vZGVsb19xZGFfZXNjIDwtIHFkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+IC4sIGRhdGEgPSBzdWJzZXRbLDE6NSw3XSkKCmBgYAoKYGBge3J9CnByZWRpY2Npb25lc19xZGFfZXNjIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19xZGFfZXNjLCBuZXdkYXRhID0gdGVzdGVvWywxOjUsN10sIG1ldGhvZCA9ICJwcmVkaWN0aXZlIikKdGFibGUoY2xhc2UsIHByZWRpY2Npb25lc19xZGFfZXNjJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9ycWRhX2VzYyA8LSBtZWFuKGNsYXNlICE9IHByZWRpY2Npb25lc19xZGFfZXNjJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JxZGFfZXNjCmBgYAoKU2UgcmVhbGl6YXJhIGVsIGFuYWxpc2lzIGNvbiBzb2xvIGRvcyBjbGFzZXM6CgoKYGBge3J9CnN1YnN1YnNldDIgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0yKSxdCnN1YnN1YnNldDEgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0xKSxdCnN1YnN1YnNldCA8LXJiaW5kKHN1YnN1YnNldDEsc3Vic3Vic2V0MikKc3Vic3Vic2V0JGNvdmVyX3R5cGUgPC0gZHJvcGxldmVscyhzdWJzdWJzZXQkY292ZXJfdHlwZSkKYGBgCgpFbCB0ZXN0IGRlIG5vcm1hbGlkYWQgbXVsdGl2YXJpYWRhOgoKYGBge3J9CmxpYnJhcnkobXZub3JtdGVzdCkKCnRlc3RpbmcgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnN1YnNldFtzdWJzdWJzZXQkY292ZXJfdHlwZT09JzEnLDE6Nl0pKQpzaGFwaXRlc3QgPC0gdGVzdGluZyRwLnZhbHVlCnRlc3RpbmcgPC0gbXNoYXBpcm8udGVzdCh0KHN1YnN1YnNldFtzdWJzdWJzZXQkY292ZXJfdHlwZT09JzInLDE6Nl0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0aW5nJHAudmFsdWUpCgpzaGFwaXRlc3QKYGBgClJlY2hhemEgbm9ybWFsaWRhZCBwYXJhIGNhZGEgdmFyaWFibGUgcGFyYSBjYWRhIG5pdmVsIGRlIGxhIHZhcmlhYmxlLgoKQ29uIGxhcyB2YXJpYWJsZXMgZXN0YW5kYXJpemFkYXM6CgpgYGB7cn0KbGlicmFyeShjbHVzdGVyU2ltKQoKI3N1YnN1YnNldF9lcyA8LSBzY2FsZShzdWJzdWJzZXRbLDE6Nl0sIGNlbnRlciA9IG1lZGlhbihzdWJzdWJzZXQpKQpzdWJzdWJzZXRfZXMgPC0gZGF0YS5Ob3JtYWxpemF0aW9uIChzdWJzdWJzZXRbLDE6Nl0sdHlwZT0ibjIiLG5vcm1hbGl6YXRpb249ImNvbHVtbiIpCnN1YnN1YnNldF9lcyA8LSBjYmluZChzdWJzdWJzZXRfZXMsc3Vic3Vic2V0Wyw3XSkKc3Vic3Vic2V0X2VzIDwtIHJlbmFtZShzdWJzdWJzZXRfZXMsIGNvdmVyX3R5cGU9YHN1YnN1YnNldFssIDddYCkKYGBgCgoKQ29uIGxhcyB2YXJpYWJsZXMgZXN0YW5kYXJpemFkYXMgeSBzYWNhbmRvIGhpbGxzaGFkZToKCmBgYHtyfQpsaWJyYXJ5KG12bm9ybXRlc3QpCgpzdWJzdWJzZXRfMiA8LSBzdWJzdWJzZXRfZXNbLGMoMTo1LDcpXQp0ZXN0aW5nIDwtIG1zaGFwaXJvLnRlc3QodChzdWJzdWJzZXRfMltzdWJzdWJzZXRfMiRjb3Zlcl90eXBlPT0nMScsMTo1XSkpCnNoYXBpdGVzdCA8LSB0ZXN0aW5nJHAudmFsdWUKdGVzdGluZyA8LSBtc2hhcGlyby50ZXN0KHQoc3Vic3Vic2V0XzJbc3Vic3Vic2V0XzIkY292ZXJfdHlwZT09JzInLDE6NV0pKQpzaGFwaXRlc3QgPC0gYXBwZW5kKHNoYXBpdGVzdCx0ZXN0aW5nJHAudmFsdWUpCgpzaGFwaXRlc3QKYGBgClJlY2hhemEsIHNpbiBzYWNhciB5IHNhY2FuZG8gaGlsbHNoYWRlLgoKVGVzdGVhbmRvIGhvbW9jZWRhc3RpY2lkYWQ6CgpgYGB7cn0KbGlicmFyeShiaW90b29scykKCmJveE0oZGF0YT1zdWJzdWJzZXRbLDE6Nl0sZ3JvdXBpbmc9c3Vic3Vic2V0Wyw3XSkKYGBgClJlY2hhemEgaWd1YWxkYWQgZGUgdmFyaWFuemFzLgoKQ29tbyBlbCB0ZXN0IE0gZGUgQm94IGVzIHNlbnNpYmxlIGEgbGEgZmFsdGEgZGUgbm9ybWFsaWRhZCwgcmVhbGl6YW1vcyBlbCBkZSBMZXZlbmU6CgpgYGB7cn0KbGlicmFyeShjYXIpCgpsZXZlbmVUZXN0KCBFbGV2YXRpb24gKyBBc3BlY3QgKyBTbG9wZSArIEhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5ICsgSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cyArIEhpbGxzaGFkZV9Ob29uIH4gc3Vic3Vic2V0JGNvdmVyX3R5cGUsIGRhdGEgPSBzdWJzdWJzZXQpCmBgYAoKRWwgdmFsb3IgZXMgbWVub3IgcXVlIGVsIG5pdmVsIGRlIHNpZ25pZmljYW5jaWEgMC4wMDEuIFJlY2hhemFtb3MgbGEgaGlwb3Rlc2lzIG51bGEgeSBjb25jbHVpbW9zIHF1ZSBsYXMgdmFyaWFuemFzIG5vIHNvbiBpZ3VhbGVzLgoKRGUgdG9kYXMgbWFuZXJhcyBoYWNlbW9zIGVsIHRlc3QgZGUgSG90ZWxsaW5nIHBhcmEgdGVzdGVhciBkaWZlcmVuY2lhIGRlIG1lZGlhczoKCmBgYHtyfQpsaWJyYXJ5KEhvdGVsbGluZykKCmZpdFByb2QgPSBob3RlbGxpbmcudGVzdCgufiBzdWJzdWJzZXQkY292ZXJfdHlwZSwgZGF0YSA9IHN1YnN1YnNldCkgCmZpdFByb2QKYGBgClJlY2hhemEgaWd1YWxkYWQgZGUgbWVkaWFzLCBwZXJvIGNvbW8gbm8gc2UgY3VtcGxlIGxhIG5vcm1hbGlkYWQgbXVsdGl2YXJpYWRhIGVzdGUgcmVzdWx0YWRvIG5vIGVzIGNvbmZpYWJsZS4KCiMgTERBIGNvbiBkb3MgY2xhc2VzCgplbCBjb25qdW50byBkZSB0ZXN0IHBhcmEgZG9zIGNsYXNlczoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQoKdGVzdGVvMiA8LXRlc3Rlb1sodGVzdGVvJGNsYXNlPT0yKSxdCnRlc3RlbzEgPC10ZXN0ZW9bKHRlc3RlbyRjbGFzZT09MSksXQp0ZXN0ZW9fMmNsYXNzIDwtcmJpbmQodGVzdGVvMSx0ZXN0ZW8yKQp0ZXN0ZW9fMmNsYXNzJGNsYXNlIDwtIGRyb3BsZXZlbHModGVzdGVvXzJjbGFzcyRjbGFzZSkKY2xhc2VfMmNsYXNzIDwtIHRlc3Rlb18yY2xhc3NbLDddCnRlc3RfMmNsYXNzIDwtdGVzdGVvXzJjbGFzc1ssMTo2XQoKYGBgCgoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fbGRhXzJjbGFzcyA8LSBsZGEoZm9ybXVsYSA9IGNvdmVyX3R5cGUgfi4gLCBkYXRhID0gc3Vic3Vic2V0KQoKYGBgCgpsYSBjbGFzaWZpY2FjaW9uIGluZ2VudWE6CgpgYGB7cn0KcHJlZGljY2lvbmVzXzJjbGFzc18wIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGFfMmNsYXNzLCBuZXdkYXRhID0gc3Vic3Vic2V0KQp0YWJsZShzdWJzdWJzZXQkY292ZXJfdHlwZSwgcHJlZGljY2lvbmVzXzJjbGFzc18wJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYApgYGB7cn0KdHJhaW5pbmdfZXJyb3JfMmNsXzAgPC0gbWVhbihzdWJzdWJzZXQkY292ZXJfdHlwZSAhPSBwcmVkaWNjaW9uZXNfMmNsYXNzXzAkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcl8yY2xfMApgYGAKCmBgYHtyfQpwcmVkaWNjaW9uZXNfMmNsYXNzIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGFfMmNsYXNzLCBuZXdkYXRhID0gdGVzdGVvXzJjbGFzcykKdGFibGUoY2xhc2VfMmNsYXNzLCBwcmVkaWNjaW9uZXNfMmNsYXNzJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yXzJjbCA8LSBtZWFuKGNsYXNlXzJjbGFzcyAhPSBwcmVkaWNjaW9uZXNfMmNsYXNzJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JfMmNsCmBgYAoKYGBge3J9CmxpYnJhcnkoa2xhUikgCgpwYXJ0aW1hdChzdWJzdWJzZXQkY292ZXJfdHlwZSB+IC4sIGRhdGEgPSBzdWJzdWJzZXQsIG1ldGhvZCA9ICJsZGEiLCBjb2wuY29ycmVjdD1OQSwgY29sLndyb25nPU5BLCBwbG90Lm1hdHJpeD1GQUxTRSxpbWFnZS5jb2xvcnMgPXJhaW5ib3coMikpCmBgYApMYSB2YXJpYWJsZSBxdWUgbWVqb3Igc2VwYXJhIGVzIGVsZXZhY2lvbi4KCgpDb24gbGFzIHZhcmlhYmxlcyBlc3RhbmRhcml6YWRhczoKCmBgYHtyfQoKbGlicmFyeShNQVNTKQoKbW9kZWxvX2xkYV8yY2xhc3NfZXN0IDwtIGxkYShmb3JtdWxhID0gY292ZXJfdHlwZSB+LiAsIGRhdGEgPSBzdWJzdWJzZXRfZXMpCgpgYGAKCmxhIGNsYXNpZmljYWNpb24gaW5nZW51YToKCmBgYHtyfQpwcmVkaWNjaW9uZXNfMmNsYXNzX2VzXzAgPC0gIHByZWRpY3Qob2JqZWN0ID0gbW9kZWxvX2xkYV8yY2xhc3NfZXN0LCBuZXdkYXRhID0gc3Vic3Vic2V0X2VzKQp0YWJsZShzdWJzdWJzZXRfZXMkY292ZXJfdHlwZSwgcHJlZGljY2lvbmVzXzJjbGFzc19lc18wJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yXzJjbF9lc18wIDwtIG1lYW4oc3Vic3Vic2V0X2VzJGNvdmVyX3R5cGUgIT0gcHJlZGljY2lvbmVzXzJjbGFzc19lc18wJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JfMmNsX2VzXzAKCmBgYApgYGB7cn0KbGlicmFyeShjbHVzdGVyU2ltKQoKI3N1YnN1YnNldF9lcyA8LSBzY2FsZShzdWJzdWJzZXRbLDE6Nl0sIGNlbnRlciA9IG1lZGlhbihzdWJzdWJzZXQpKQp0ZXN0ZW9fZXMgPC0gZGF0YS5Ob3JtYWxpemF0aW9uICh0ZXN0ZW9fMmNsYXNzWywxOjZdLHR5cGU9Im4yIixub3JtYWxpemF0aW9uPSJjb2x1bW4iKQp0ZXN0ZW9fZXMgPC0gY2JpbmQodGVzdGVvX2VzLHRlc3Rlb18yY2xhc3MkY2xhc2UpCnRlc3Rlb19lcyA8LSByZW5hbWUodGVzdGVvX2VzLCBjbGFzZT1gdGVzdGVvXzJjbGFzcyRjbGFzZWApCmBgYAoKCmBgYHtyfQpwcmVkaWNjaW9uZXNfMmNsYXNzX2VzIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19sZGFfMmNsYXNzX2VzdCwgbmV3ZGF0YSA9IHRlc3Rlb19lcykKdGFibGUodGVzdGVvX2VzJGNsYXNlLCBwcmVkaWNjaW9uZXNfMmNsYXNzX2VzJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yXzJjbF9lcyA8LSBtZWFuKHRlc3Rlb19lcyRjbGFzZSAhPSBwcmVkaWNjaW9uZXNfMmNsYXNzX2VzJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JfMmNsX2VzCgpgYGAKSGFjaWVuZG8gZWwgZGlzY3JpbWluYW50ZSByb2J1c3RvOgoKYGBge3J9CgpsaWJyYXJ5KE1BU1MpCgptb2RlbG9fcWRhXzJjbGFzc19lc3QgPC0gcWRhKGZvcm11bGEgPSBjb3Zlcl90eXBlIH4uICwgZGF0YSA9IHN1YnN1YnNldF9lcykKCmBgYAoKYGBge3J9CnByZWRpY2Npb25lc3FkYV8yY19lc18wIDwtICBwcmVkaWN0KG9iamVjdCA9IG1vZGVsb19xZGFfMmNsYXNzX2VzdCwgbmV3ZGF0YSA9IHN1YnN1YnNldF9lcykKdGFibGUoc3Vic3Vic2V0X2VzJGNvdmVyX3R5cGUsIHByZWRpY2Npb25lc3FkYV8yY19lc18wJGNsYXNzLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9ycWRhXzJjbF9lc18wIDwtIG1lYW4oc3Vic3Vic2V0X2VzJGNvdmVyX3R5cGUgIT0gcHJlZGljY2lvbmVzcWRhXzJjX2VzXzAkY2xhc3MpICogMTAwIAp0cmFpbmluZ19lcnJvcnFkYV8yY2xfZXNfMAoKYGBgCmBgYHtyfQpwcmVkaWNjaW9uZXNxZGFfMmNfZXMgPC0gIHByZWRpY3Qob2JqZWN0ID0gbW9kZWxvX3FkYV8yY2xhc3NfZXN0LCBuZXdkYXRhID0gdGVzdGVvX2VzKQp0YWJsZSh0ZXN0ZW9fZXMkY2xhc2UsIHByZWRpY2Npb25lc3FkYV8yY19lcyRjbGFzcywgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKCmBgYHtyfQp0cmFpbmluZ19lcnJvcnFkYV8yY2xfZXMgPC0gbWVhbih0ZXN0ZW9fZXMkY2xhc2UgIT0gcHJlZGljY2lvbmVzcWRhXzJjX2VzJGNsYXNzKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3JxZGFfMmNsX2VzCgpgYGAKSW5jbHVzbyBlbXBlb3JhIHVuIHBvY28uCgojIyBTdXBwb3J0IFZlY3RvciBtYWNoaW5lCgpDbGFzaWZpY2FuZG8gY29uIHN2bSBlbCBkYXRhc2V0IGNvbiBsYXMgNyBjbGFzZXM6CgpgYGB7cn0KbGlicmFyeShlMTA3MSkKCm1vZGVsLnN2bSA9IHN2bSggc3Vic2V0JGNvdmVyX3R5cGUgfiAuLCBkYXRhID0gc3Vic2V0WyxjKDE6NSw3KV0sIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMTAsIHNjYWxlID0gVFJVRSkKcHJpbnQobW9kZWwuc3ZtKQoKYGBgCmBgYHtyfQpwcmVkaWNjaW9uZXNfc3ZtID0gcHJlZGljdChtb2RlbC5zdm0sIHN1YnNldFssYygxOjUsNyldKQoKdGFibGUoc3Vic2V0JGNvdmVyX3R5cGUsIHByZWRpY2Npb25lc19zdm0sIGRubiA9IGMoIkNsYXNlIHJlYWwiLCAiQ2xhc2UgcHJlZGljaGEiKSkKYGBgCmBgYHtyfQp0cmFpbmluZ19lcnJvcl9zdm1fMCA8LSBtZWFuKHN1YnNldCRjb3Zlcl90eXBlICE9IHByZWRpY2Npb25lc19zdm0pICogMTAwIAp0cmFpbmluZ19lcnJvcl9zdm1fMApgYGAKRWwga2VybmVsIHF1ZSBtZWpvciBkYSBlcyBlbCByYWRpYWwuIAoKYGBge3J9CnByZWRpY2Npb25lc19zdm1fdGUgPSBwcmVkaWN0KG1vZGVsLnN2bSwgdGVzdGVvKQoKdGFibGUodGVzdGVvJGNsYXNlLCBwcmVkaWNjaW9uZXNfc3ZtX3RlLCBkbm4gPSBjKCJDbGFzZSByZWFsIiwgIkNsYXNlIHByZWRpY2hhIikpCmBgYAoKYGBge3J9CnRyYWluaW5nX2Vycm9yX3N2bSA8LSBtZWFuKHRlc3RlbyRjbGFzZSAhPSBwcmVkaWNjaW9uZXNfc3ZtX3RlKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3Jfc3ZtCmBgYAoKU2kgbG8gaGFjZW1vcyBjb24gbWVub3MgdmFyaWFibGVzOgoKYGBge3J9CnN1YnN1YnNldDEgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0xKSxdCnN1YnN1YnNldDIgPC1zdWJzZXRbKHN1YnNldCRjb3Zlcl90eXBlPT0yKSxdCnN1YnN1YnNldCA8LXJiaW5kKHN1YnN1YnNldDEsc3Vic3Vic2V0MikKc3Vic3Vic2V0JGNvdmVyX3R5cGUgPC0gZHJvcGxldmVscyhzdWJzdWJzZXQkY292ZXJfdHlwZSkKYGBgCgoKYGBge3J9CmxpYnJhcnkoZTEwNzEpCgptb2RlbC5zdm1fMiA9IHN2bSggc3Vic3Vic2V0JGNvdmVyX3R5cGUgfiAuLCBkYXRhID0gc3Vic3Vic2V0WyxjKDE6NSw3KV0sIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMTAsIHNjYWxlID0gVFJVRSkKcHJpbnQobW9kZWwuc3ZtKQoKYGBgCgoKYGBge3J9CnByZWRpY2Npb25lc19zdm1fMiA9IHByZWRpY3QobW9kZWwuc3ZtXzIsIHN1YnN1YnNldFssYygxOjUsNyldKQoKdGFibGUoc3Vic3Vic2V0JGNvdmVyX3R5cGUsIHByZWRpY2Npb25lc19zdm1fMiwgZG5uID0gYygiQ2xhc2UgcmVhbCIsICJDbGFzZSBwcmVkaWNoYSIpKQpgYGAKYGBge3J9CnRyYWluaW5nX2Vycm9yX3N2bV8yIDwtIG1lYW4oc3Vic3Vic2V0JGNvdmVyX3R5cGUgIT0gcHJlZGljY2lvbmVzX3N2bV8yKSAqIDEwMCAKdHJhaW5pbmdfZXJyb3Jfc3ZtXzIKYGBgCkFob3JhIGNvbiBlbCB0ZXN0OgoKYGBge3J9CnByZWRpY2Npb25lc19zdm1fdGVfMiA9IHByZWRpY3QobW9kZWwuc3ZtXzIsIHRlc3Rlb18yY2xhc3MpCgp0YWJsZSh0ZXN0ZW9fMmNsYXNzJGNsYXNlLCBwcmVkaWNjaW9uZXNfc3ZtX3RlXzIsIGRubiA9IGMoIkNsYXNlIHJlYWwiLCAiQ2xhc2UgcHJlZGljaGEiKSkKYGBgCgpgYGB7cn0KdHJhaW5pbmdfZXJyb3Jfc3ZtXzJfdGUgPC0gbWVhbih0ZXN0ZW9fMmNsYXNzJGNsYXNlICE9IHByZWRpY2Npb25lc19zdm1fdGVfMikgKiAxMDAgCnRyYWluaW5nX2Vycm9yX3N2bV8yX3RlCmBgYApHcmFmaWNhbmRvIHBhcmEgdG9kYXMgbGFzIGNsYXNlczoKCmBgYHtyfQpwbG90KG1vZGVsLnN2bSwgZGF0YT1zdWJzZXRbLGMoMTo1LDcpXSxFbGV2YXRpb24gfiBBc3BlY3QsICBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bSwgZGF0YT1zdWJzZXRbLGMoMTo1LDcpXSwgRWxldmF0aW9uIH4gU2xvcGUsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBFbGV2YXRpb24gfiBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm0sIGRhdGE9c3Vic2V0WyxjKDE6NSw3KV0sIEVsZXZhdGlvbiB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBBc3BlY3QgfiBTbG9wZSwgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKYGBgCgpgYGB7cn0KcGxvdChtb2RlbC5zdm0sIGRhdGE9c3Vic2V0WyxjKDE6NSw3KV0sIEFzcGVjdCB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bSwgZGF0YT1zdWJzZXRbLGMoMTo1LDcpXSwgQXNwZWN0IH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm0sIGRhdGE9c3Vic2V0WyxjKDE6NSw3KV0sIFNsb3BlIH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBTbG9wZSB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtLCBkYXRhPXN1YnNldFssYygxOjUsNyldLCBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCgpgYGAKYGBge3J9CnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sRWxldmF0aW9uIH4gQXNwZWN0LCAgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm1fMiwgZGF0YT1zdWJzdWJzZXRbLGMoMTo1LDcpXSwgRWxldmF0aW9uIH4gU2xvcGUsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIEVsZXZhdGlvbiB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bV8yLCBkYXRhPXN1YnN1YnNldFssYygxOjUsNyldLCBFbGV2YXRpb24gfiBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQpwbG90KG1vZGVsLnN2bV8yLCBkYXRhPXN1YnN1YnNldFssYygxOjUsNyldLCBBc3BlY3QgfiBTbG9wZSwgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKYGBgCgpgYGB7cn0KcGxvdChtb2RlbC5zdm1fMiwgZGF0YT1zdWJzdWJzZXRbLGMoMTo1LDcpXSwgQXNwZWN0IH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIEFzcGVjdCB+IEhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIFNsb3BlIH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yLnBhbGV0dGU9dG9wby5jb2xvcnMpCnBsb3QobW9kZWwuc3ZtXzIsIGRhdGE9c3Vic3Vic2V0WyxjKDE6NSw3KV0sIFNsb3BlIH4gSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3IucGFsZXR0ZT10b3BvLmNvbG9ycykKcGxvdChtb2RlbC5zdm1fMiwgZGF0YT1zdWJzdWJzZXRbLGMoMTo1LDcpXSwgSG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3kgfiBIb3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvci5wYWxldHRlPXRvcG8uY29sb3JzKQoKYGBgCgoKU2UgZGlidWphbiBtYXMgZnJvbnRlcmFzIGN1YW5kbyBsYSB2YXJpYWJsZSBFbGV2YXRpb24gZXN0YSBwcmVzZW50ZS4KCiMjIENsYXNpZmljYWNpb24gbm8gc3VwZXJ2aXNhZGEKClBhcmEgbGEgY2xhc2lmaWNhY2lvbiBubyBzdXBlcnZpc2FkYSwgY29tZW56YW1vcyBjb24gbWV0b2RvcyBqZXJhcnF1aWNvcyBwb3JxdWUgay1tZWFucyBlcyBzZW5zaWJsZSBhIG91dGxpZXJzOgoKYGBge3J9CmRhdG9zX2NsdXN0IDwtIHN1YnNldAptYXRfZGlzdCA8LSBkaXN0KHggPSBzdWJzZXQsIG1ldGhvZCA9ICJldWNsaWRlYW4iKSAKCmBgYAoKRGVuZHJvZ3JhbWFzIHBhcmEgZGlzdGludG9zIG1ldG9kb3MsIHN1cG9uZW1vcyA3IGNsdXN0ZXJzOgoKYGBge3J9CmhjX2NvbXBsZXRlIDwtIGhjbHVzdChkID0gbWF0X2Rpc3QsIG1ldGhvZCA9ICJjb21wbGV0ZSIpIApoY19hdmVyYWdlICA8LSBoY2x1c3QoZCA9IG1hdF9kaXN0LCBtZXRob2QgPSAiYXZlcmFnZSIpCmhjX3NpbmdsZSAgIDwtIGhjbHVzdChkID0gbWF0X2Rpc3QsIG1ldGhvZCA9ICJzaW5nbGUiKQpoY193YXJkICAgICA8LSBoY2x1c3QoZCA9IG1hdF9kaXN0LCBtZXRob2QgPSAid2FyZC5EMiIpCmhjX2NuICAgICA8LSBoY2x1c3QoZCA9IG1hdF9kaXN0LCBtZXRob2QgPSAiY2VudHJvaWQiKQpgYGAKCkNvbnN0cnV5ZW5kbyBsb3MgZGVuZHJvZ3JhbWFzOgoKYGBge3J9CmNhbnRpZGFkX2NsdXN0ZXJzID0gNwoKcGxvdChoY19jb21wbGV0ZSkKcmVjdC5oY2x1c3QoaGNfY29tcGxldGUsIGs9Y2FudGlkYWRfY2x1c3RlcnMsIGJvcmRlcj0icmVkIikgIwoKamVyX2NvbXBsZXRlPC1jdXRyZWUoaGNfY29tcGxldGUsaz1jYW50aWRhZF9jbHVzdGVycykgICAgICAgICAgICMKZGF0b3NfY2x1c3QkamVyX2NvbXBsZXRlPWplcl9jb21wbGV0ZQoKYGBgCmBgYHtyfQpjYW50aWRhZF9jbHVzdGVycyA9IDcKCnBsb3QoaGNfYXZlcmFnZSkKcmVjdC5oY2x1c3QoaGNfYXZlcmFnZSwgaz1jYW50aWRhZF9jbHVzdGVycywgYm9yZGVyPSJyZWQiKSAjCgpqZXJfYXZlcmFnZTwtY3V0cmVlKGhjX2F2ZXJhZ2Usaz1jYW50aWRhZF9jbHVzdGVycykgICAgICAgICAgICMKZGF0b3NfY2x1c3QkamVyX2F2ZXJhZ2U9amVyX2F2ZXJhZ2UKYGBgCgpgYGB7cn0KY2FudGlkYWRfY2x1c3RlcnMgPSA3CgpwbG90KGhjX3NpbmdsZSkKcmVjdC5oY2x1c3QoaGNfc2luZ2xlLCBrPWNhbnRpZGFkX2NsdXN0ZXJzLCBib3JkZXI9InJlZCIpICMKCmplcl9zaW5nbGU8LWN1dHJlZShoY19zaW5nbGUsaz1jYW50aWRhZF9jbHVzdGVycykgICAgICAgICAgICMKI2RhdG9zJGplcl9jb21wbGV0ZT1qZXJfY29tcGxldGUKYGBgCgpEYSBmZWlzaW1vLi4uCgpgYGB7cn0KY2FudGlkYWRfY2x1c3RlcnMgPSA3CgpwbG90KGhjX3dhcmQpCnJlY3QuaGNsdXN0KGhjX3dhcmQsIGs9Y2FudGlkYWRfY2x1c3RlcnMsIGJvcmRlcj0icmVkIikgIwoKamVyX3dhcmQ8LWN1dHJlZShoY193YXJkLGs9Y2FudGlkYWRfY2x1c3RlcnMpICAgICAgICAgICAjCmRhdG9zX2NsdXN0JGplcl93YXJkPWplcl93YXJkCmBgYAoKCmBgYHtyfQpjYW50aWRhZF9jbHVzdGVycyA9IDcKCnBsb3QoaGNfY24pCnJlY3QuaGNsdXN0KGhjX2NuLCBrPWNhbnRpZGFkX2NsdXN0ZXJzLCBib3JkZXI9InJlZCIpICMKCmplcl9jbjwtY3V0cmVlKGhjX2NuLGs9Y2FudGlkYWRfY2x1c3RlcnMpICAgICAgICAgICAjCmRhdG9zX2NsdXN0JGplcl9jbj1qZXJfY24KYGBgCgoKTG9zIGNvZWZpY2llbnRlcyBkZSBjb3JyZWxhY2lvbiBjb2ZlbmV0aWNhOgoKYGBge3J9CmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfY29tcGxldGUpKQpjb3IoeCA9IG1hdF9kaXN0LCBjb3BoZW5ldGljKGhjX2F2ZXJhZ2UpKQpjb3IoeCA9IG1hdF9kaXN0LCBjb3BoZW5ldGljKGhjX3NpbmdsZSkpCmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfd2FyZCkpCmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfY24pKQpgYGAKRWwgbGlua2FnZSBzaW5nbGUgZXMgZWwgcGVvci4KCmBgYHtyfQpkYXRvc19jbHVzdCRqZXJfY29tcGxldGUgPC0gYXMuZmFjdG9yKGRhdG9zX2NsdXN0JGplcl9jbikKcDEgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1FbGV2YXRpb24sIHk9QXNwZWN0LCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAyIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PVNsb3BlLCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpCnAyICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAzIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpCnAzICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnA0IDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yPWplcl9jbikpICsKICBnZW9tX3BvaW50KCkKcDQgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKQpgYGAKCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGVhc3lHZ3Bsb3QyKQoKZGF0b3NfY2x1c3QkamVyX2NvbXBsZXRlIDwtIGFzLmZhY3RvcihkYXRvc19jbHVzdCRqZXJfY24pCgpwMiA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1TbG9wZSwgY29sb3I9amVyX2NuLCBhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwMyA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9amVyX2NuLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArbGFicyh5PSdIX0h5ZHJvbG9neScpCgoKcDQgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1FbGV2YXRpb24sIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9amVyX2NuLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStsYWJzKHk9J0hfUm9hZHdheXMnKQoKcDcgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Bc3BlY3QsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9amVyX2NuLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStsYWJzKHk9J0hfUm9hZHdheXMnKQoKcDkgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1TbG9wZSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1qZXJfY24sYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2xhYnMoeT0nSF9Sb2Fkd2F5cycpCgpwMTAgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1qZXJfY24sYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2xhYnMoeD0nSF9IeWRyb2xvZ3knLHk9J0hfUm9hZHdheXMnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocDIscDMscDQscDcscDkscDEwLGNvbHM9MykKCnBuZygnamVyYXJxdWljYWwucG5nJykKCmdncGxvdDIubXVsdGlwbG90KHAyLHAzLHA0LHA3LHA5LHAxMCxjb2xzPTMpCgpkZXYub2ZmKCkKYGBgCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQoKcCA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUFzcGVjdCwgeT1TbG9wZSwgY29sb3I9amVyX2NuKSkgKwogIGdlb21fcG9pbnQoKQpwICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Bc3BlY3QsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19IeWRyb2xvZ3ksIGNvbG9yPWplcl9jbikpICsKICBnZW9tX3BvaW50KCkKcCArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpCgpwIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9QXNwZWN0LCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yPWplcl9jbikpICsKICBnZW9tX3BvaW50KCkKcCArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpCgoKcCA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PVNsb3BlLCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCBjb2xvcj1qZXJfY24pKSArCiAgZ2VvbV9wb2ludCgpCnAgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKQoKYGBgCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCgpwIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9U2xvcGUsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9amVyX2NuKSkgKwogIGdlb21fcG9pbnQoKQpwICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikKCnAgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1qZXJfY29tcGxldGUpKSArCiAgZ2VvbV9wb2ludCgpCnAgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKQoKCmBgYApTb2xvIGxvIGhpY2UgY29uIGVsIHByaW1lciBtZXRvZG8gZGUgY2x1c3RlciBqZXJhcnF1aWNvIHF1ZSBmdWUgZWwgcXVlIG1lam9yIGRpbyBlbCBjb2VmaWNpZW50ZSBjb2ZyZW5ldGljby4KCgoKCmBgYHtyIGVjaG89VFJVRX0KbGlicmFyeShjbHVzdGVyKQoKZGF0b3Nfa21lYW5zID0gZGF0b3NfY2x1c3RbMTo1XQoKY2FudGlkYWRfY2x1c3RlcnM9NwoKQ0wgID0ga21lYW5zKHNjYWxlKGRhdG9zX2ttZWFucyksY2FudGlkYWRfY2x1c3RlcnMpCmRhdG9zX2ttZWFucyRrbWVhbnMgPSBDTCRjbHVzdGVyCmBgYAoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShlYXN5R2dwbG90MikKCmRhdG9zX2ttZWFucyRrbWVhbnMgPC0gYXMuZmFjdG9yKGRhdG9zX2ttZWFucyRrbWVhbnMpCgpwMSA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1Bc3BlY3QsIGNvbG9yPWRhdG9zX2ttZWFucyRrbWVhbnMsIGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnAyIDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9RWxldmF0aW9uLCB5PVNsb3BlLCBjb2xvcj1kYXRvc19rbWVhbnMka21lYW5zLCBhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwMyA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUVsZXZhdGlvbiwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgK2xhYnMoeT0nSF9IeWRyb2xvZ3knKQoKcDQgPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1FbGV2YXRpb24sIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrbGFicyh5PSdIX1JvYWR3YXlzJykKCnA1IDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9QXNwZWN0LCB5PVNsb3BlLCBjb2xvcj1kYXRvc19rbWVhbnMka21lYW5zLCBhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwNiA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUFzcGVjdCwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucywgYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArbGFicyh5PSdIX0h5ZHJvbG9neScpCgpwNyA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUFzcGVjdCwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX1JvYWR3YXlzLCBjb2xvcj1kYXRvc19rbWVhbnMka21lYW5zLGFscGhhPTAuNSkpICsKICBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKStsYWJzKHk9J0hfUm9hZHdheXMnKQoKcDggPC0gZ2dwbG90KGRhdG9zX2NsdXN0LCBhZXMoeD1TbG9wZSwgeT1Ib3Jpem9udGFsX0Rpc3RhbmNlX1RvX0h5ZHJvbG9neSwgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrbGFicyh5PSdIX1JvYWR3YXlzJykKCnA5IDwtIGdncGxvdChkYXRvc19jbHVzdCwgYWVzKHg9U2xvcGUsIHk9SG9yaXpvbnRhbF9EaXN0YW5jZV9Ub19Sb2Fkd2F5cywgY29sb3I9ZGF0b3Nfa21lYW5zJGttZWFucyxhbHBoYT0wLjUpKSArCiAgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGU9IkRhcmsyIikrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrbGFicyh5PSdIX1JvYWR3YXlzJykKCnAxMCA8LSBnZ3Bsb3QoZGF0b3NfY2x1c3QsIGFlcyh4PUhvcml6b250YWxfRGlzdGFuY2VfVG9fSHlkcm9sb2d5LCB5PUhvcml6b250YWxfRGlzdGFuY2VfVG9fUm9hZHdheXMsIGNvbG9yPWRhdG9zX2ttZWFucyRrbWVhbnMsYWxwaGE9MC41KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpK2xhYnMoeD0nSF9IeWRyb2xvZ3knLHk9J0hfUm9hZHdheXMnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocDEscDIscDMscDQscDUscDYscDcscDgscDkscDEwLGNvbHM9MykKCnBuZygnamVyYXJxdWljYWxfMi5wbmcnKQoKZ2dwbG90Mi5tdWx0aXBsb3QocDEscDIscDMscDQscDUscDYscDcscDgscDkscDEwLGNvbHM9MykKCmRldi5vZmYoKQpgYGAKCgo=